You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2023/05/06 15:00:10 UTC
[sis] branch master updated (410b3a1f6a -> b98a31fa9b)
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/sis.git
from 410b3a1f6a Merge branch 'geoapi-3.1': remove contravariance in filters and expressions. https://issues.apache.org/jira/browse/SIS-578
add 7e19b7fe50 Restore contravariance on `FeatureQuery` class. This is a much smaller scope than the previous contravariance on `Filter` interface. Furthermore the addition of `getResourceClass()` in previous commit allows some type checks if needed.
add d8bbaf214a Clarify which `Feature` instance is used for the different projection types. Other opportunistic clarifications as we tried to verify the behavior. Replace `ExpressionOperation` by `LinkOperation` when applicable.
add f308b11bc8 Rename `ProjectionType.COMPLETING` ad `COMPUTING`.
add 31126879d2 Retrofit `GroupAsPolylineOperation` together with all other feature operations. It required a generalization for working on attributes as well as associations.
add 3d84f4f38a More conservative handling of nested operations in `GroupAsPolylineOperation`.
add d6577877ca 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.
add 2bf0bc48a0 Clarify whether to inherit the intrinsic properties of a function or to inherit only the transitive properties.
add 6ad64f5ffa Use the information about volatile function in order to determine whether to apply an optimization.
add b486f990bc Merge branch 'geoapi-4.0' into geoapi-3.1.
new b98a31fa9b Merge branch 'geoapi-3.1'.
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../org/apache/sis/feature/AbstractFeature.java | 12 +-
.../apache/sis/feature/DefaultAssociationRole.java | 8 +-
.../java/org/apache/sis/feature/DenseFeature.java | 1 +
.../org/apache/sis/feature/EnvelopeOperation.java | 20 +-
.../apache/sis/feature/ExpressionOperation.java | 66 +++++-
.../org/apache/sis/feature/FeatureOperations.java | 74 +++++--
.../main/java/org/apache/sis/feature/Features.java | 39 +++-
.../sis/feature/GroupAsPolylineOperation.java | 242 +++++++++++++++++++++
.../java/org/apache/sis/feature/LinkOperation.java | 2 +-
.../org/apache/sis/feature/OperationResult.java | 66 ++++++
.../java/org/apache/sis/feature/SparseFeature.java | 1 +
.../apache/sis/feature/StringJoinOperation.java | 12 +-
.../org/apache/sis/filter/AssociationValue.java | 12 +
.../org/apache/sis/filter/ConvertFunction.java | 12 +
.../java/org/apache/sis/filter/LeafExpression.java | 26 +++
.../java/org/apache/sis/filter/Optimization.java | 49 ++++-
.../java/org/apache/sis/filter/PropertyValue.java | 1 +
.../sis/internal/coverage/j2d/ObservableImage.java | 2 +-
.../sis/internal/feature/FeatureExpression.java | 19 +-
.../apache/sis/internal/feature/Geometries.java | 33 ---
.../sis/internal/feature/GeometryWrapper.java | 6 +-
.../apache/sis/internal/feature/esri/Wrapper.java | 4 +-
.../sis/internal/feature/j2d/PointWrapper.java | 2 +-
.../apache/sis/internal/feature/j2d/Wrapper.java | 2 +-
.../apache/sis/internal/feature/jts/Wrapper.java | 2 +-
.../java/org/apache/sis/internal/filter/Node.java | 87 ++++++++
.../apache/sis/{ => internal}/filter/XPath.java | 8 +-
.../apache/sis/feature/FeatureOperationsTest.java | 2 +-
.../sis/feature/GroupAsPolylineOperationTest.java | 65 ++++++
.../org/apache/sis/filter/LogicalFilterTest.java | 26 +++
.../test/java/org/apache/sis/filter/XPathTest.java | 1 +
.../sis/internal/feature/GeometriesTestCase.java | 2 +-
.../apache/sis/test/suite/FeatureTestSuite.java | 1 +
.../java/org/apache/sis/math/FunctionProperty.java | 85 +++++++-
.../org/apache/sis/math/FunctionPropertyTest.java | 28 ++-
.../apache/sis/test/suite/UtilityTestSuite.java | 3 +-
.../java/org/apache/sis/storage/FeatureQuery.java | 112 +++++++---
.../java/org/apache/sis/storage/FeatureSubset.java | 6 +-
.../org/apache/sis/storage/FeatureQueryTest.java | 8 +-
.../storage/gpx/GroupAsPolylineOperation.java | 207 ------------------
.../org/apache/sis/internal/storage/gpx/Types.java | 31 ++-
41 files changed, 1007 insertions(+), 378 deletions(-)
create mode 100644 core/sis-feature/src/main/java/org/apache/sis/feature/GroupAsPolylineOperation.java
create mode 100644 core/sis-feature/src/main/java/org/apache/sis/feature/OperationResult.java
rename core/sis-feature/src/main/java/org/apache/sis/{ => internal}/filter/XPath.java (90%)
create mode 100644 core/sis-feature/src/test/java/org/apache/sis/feature/GroupAsPolylineOperationTest.java
copy storage/sis-geotiff/src/test/java/org/apache/sis/storage/geotiff/TagsTest.java => core/sis-utility/src/test/java/org/apache/sis/math/FunctionPropertyTest.java (53%)
delete mode 100644 storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/GroupAsPolylineOperation.java
[sis] 01/01: Merge branch 'geoapi-3.1'.
Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sis.git
commit b98a31fa9b860e69acc8fa89357e24f52d63bffc
Merge: 410b3a1f6a b486f990bc
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sat May 6 16:59:39 2023 +0200
Merge branch 'geoapi-3.1'.
.../org/apache/sis/feature/AbstractFeature.java | 12 +-
.../apache/sis/feature/DefaultAssociationRole.java | 8 +-
.../java/org/apache/sis/feature/DenseFeature.java | 1 +
.../org/apache/sis/feature/EnvelopeOperation.java | 20 +-
.../apache/sis/feature/ExpressionOperation.java | 66 +++++-
.../org/apache/sis/feature/FeatureOperations.java | 74 +++++--
.../main/java/org/apache/sis/feature/Features.java | 39 +++-
.../sis/feature/GroupAsPolylineOperation.java | 242 +++++++++++++++++++++
.../java/org/apache/sis/feature/LinkOperation.java | 2 +-
.../org/apache/sis/feature/OperationResult.java | 66 ++++++
.../java/org/apache/sis/feature/SparseFeature.java | 1 +
.../apache/sis/feature/StringJoinOperation.java | 12 +-
.../org/apache/sis/filter/AssociationValue.java | 12 +
.../org/apache/sis/filter/ConvertFunction.java | 12 +
.../java/org/apache/sis/filter/LeafExpression.java | 26 +++
.../java/org/apache/sis/filter/Optimization.java | 49 ++++-
.../java/org/apache/sis/filter/PropertyValue.java | 1 +
.../sis/internal/coverage/j2d/ObservableImage.java | 2 +-
.../sis/internal/feature/FeatureExpression.java | 19 +-
.../apache/sis/internal/feature/Geometries.java | 33 ---
.../sis/internal/feature/GeometryWrapper.java | 6 +-
.../apache/sis/internal/feature/esri/Wrapper.java | 4 +-
.../sis/internal/feature/j2d/PointWrapper.java | 2 +-
.../apache/sis/internal/feature/j2d/Wrapper.java | 2 +-
.../apache/sis/internal/feature/jts/Wrapper.java | 2 +-
.../java/org/apache/sis/internal/filter/Node.java | 87 ++++++++
.../apache/sis/{ => internal}/filter/XPath.java | 8 +-
.../apache/sis/feature/FeatureOperationsTest.java | 2 +-
.../sis/feature/GroupAsPolylineOperationTest.java | 65 ++++++
.../org/apache/sis/filter/LogicalFilterTest.java | 26 +++
.../test/java/org/apache/sis/filter/XPathTest.java | 1 +
.../sis/internal/feature/GeometriesTestCase.java | 2 +-
.../apache/sis/test/suite/FeatureTestSuite.java | 1 +
.../java/org/apache/sis/math/FunctionProperty.java | 85 +++++++-
.../org/apache/sis/math/FunctionPropertyTest.java} | 30 ++-
.../apache/sis/test/suite/UtilityTestSuite.java | 3 +-
.../java/org/apache/sis/storage/FeatureQuery.java | 112 +++++++---
.../java/org/apache/sis/storage/FeatureSubset.java | 6 +-
.../org/apache/sis/storage/FeatureQueryTest.java | 8 +-
.../storage/gpx/GroupAsPolylineOperation.java | 207 ------------------
.../org/apache/sis/internal/storage/gpx/Types.java | 31 ++-
41 files changed, 1009 insertions(+), 378 deletions(-)
diff --cc core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java
index fb74497a08,becfe57dfb..b80b9eea0d
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java
@@@ -427,10 -446,13 +434,13 @@@ public abstract class AbstractFeature i
* Executes the parameterless operation of the given name and sets the value of its result.
* This method is the complement of {@link #getOperationValue(String)} for subclasses where
* some properties may be operations. Not all operations accept assignments,
- * but the {@linkplain FeatureOperations#link link} operation for instance does.
+ * but the {@linkplain FeatureOperations#link link} and
+ * {@linkplain FeatureOperations#compound compound} operations (for instances) do.
+ * Whether an operation is writable or not depends on whether the computed {@link Property}
+ * supports {@link Attribute#setValue(Object)} or {@link FeatureAssociation#setValue(Feature)}.
*
* @param name the name of the operation to execute. The caller is responsible to ensure that the
- * property type for that name is an instance of {@link Operation}.
+ * property type for that name is an instance of {@link AbstractOperation}.
* @param value the value to assign to the result of the named operation.
* @throws IllegalStateException if the operation of the given name does not accept assignment.
*
diff --cc core/sis-feature/src/main/java/org/apache/sis/feature/EnvelopeOperation.java
index db3bdd0056,88594a2fa2..05335b0335
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/EnvelopeOperation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/EnvelopeOperation.java
@@@ -277,9 -281,8 +272,8 @@@ final class EnvelopeOperation extends A
/**
* Creates a new attribute for the given feature.
*/
- Result(final Feature feature) {
+ Result(final AbstractFeature feature) {
- super(resultType);
- this.feature = feature;
+ super(resultType, feature);
}
/**
diff --cc core/sis-feature/src/main/java/org/apache/sis/feature/ExpressionOperation.java
index 0d0531ee49,7b897b979a..2d9cc34278
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/ExpressionOperation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/ExpressionOperation.java
@@@ -58,12 -63,13 +58,12 @@@ final class ExpressionOperation<V> exte
* The expression on which to delegate the execution of this operation.
*/
@SuppressWarnings("serial") // Not statically typed as serializable.
- private final Function<AbstractFeature, ? extends V> expression;
- private final Function<? super Feature, ? extends V> expression;
++ private final Function<? super AbstractFeature, ? extends V> expression;
/**
* The type of result of evaluating the expression.
*/
- private final DefaultAttributeType<? super V> result;
- @SuppressWarnings("serial") // Apache SIS implementations are serializable.
- private final AttributeType<V> resultType;
++ private final DefaultAttributeType<V> resultType;
/**
* The name of all feature properties that are known to be read by the expression.
@@@ -78,17 -84,35 +78,35 @@@
*
* @param identification the name of the operation, together with optional information.
* @param expression the expression to evaluate on feature instances.
- * @param result type of values computed by the expression.
+ * @param resultType type of values computed by the expression.
*/
- ExpressionOperation(final Map<String,?> identification,
- final Function<AbstractFeature, ? extends V> expression,
- final DefaultAttributeType<? super V> result)
+ static <V> AbstractOperation create(final Map<String,?> identification,
- final Function<? super Feature, ? extends V> expression,
- final AttributeType<? super V> resultType)
++ final Function<? super AbstractFeature, ? extends V> expression,
++ final DefaultAttributeType<? super V> resultType)
+ {
+ if (expression instanceof ValueReference<?,?>) {
+ final String xpath = ((ValueReference<?,?>) expression).getXPath();
+ if (xpath.equals(resultType.getName().toString())) {
+ return new LinkOperation(identification, resultType);
+ }
+ }
+ return new ExpressionOperation<>(identification, expression, resultType);
+ }
+
+ /**
+ * Creates a generic operation when no optimized case has been identifier.
+ */
+ private ExpressionOperation(final Map<String,?> identification,
- final Function<? super Feature, ? extends V> expression,
- final AttributeType<V> resultType)
++ final Function<? super AbstractFeature, ? extends V> expression,
++ final DefaultAttributeType<V> resultType)
{
super(identification);
this.expression = expression;
- this.result = result;
+ this.resultType = resultType;
if (expression instanceof Expression<?,?>) {
- dependencies = DependencyFinder.search((Expression<AbstractFeature,?>) expression);
+ @SuppressWarnings("unchecked")
- var c = (Expression<Feature,?>) expression; // Cast is okay because we will not pass or request any `Feature` instance.
++ var c = (Expression<AbstractFeature,?>) expression; // Cast is okay because we will not pass or request any `Feature` instance.
+ dependencies = DependencyFinder.search(c);
} else {
dependencies = Set.of();
}
@@@ -106,8 -130,8 +124,8 @@@
* Returns the expected result type.
*/
@Override
- public IdentifiedType getResult() {
+ public AbstractIdentifiedType getResult() {
- return result;
+ return resultType;
}
/**
@@@ -128,10 -152,34 +146,34 @@@
* @return the computed property from the given feature.
*/
@Override
- public Property apply(final Feature feature, ParameterValueGroup parameters) {
+ public Property apply(final AbstractFeature feature, ParameterValueGroup parameters) {
- final AbstractAttribute<? super V> instance = result.newInstance();
- instance.setValue(expression.apply(feature));
- return instance;
+ return new Result(feature);
+ }
+
+ /**
+ * The attributes that delegates computation to the expression.
+ * Value is calculated each time it is accessed.
+ */
+ private final class Result extends OperationResult<V> {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = -19004252522001532L;
+
+ /**
+ * Creates a new attribute for the given feature.
+ */
- Result(final Feature feature) {
++ Result(final AbstractFeature feature) {
+ super(resultType, feature);
+ }
+
+ /**
+ * Delegates the computation to the user-supplied expression.
+ */
+ @Override
+ public V getValue() {
+ return expression.apply(feature);
+ }
}
/**
diff --cc core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java
index 98b451ddd7,7986a36733..3e79790812
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java
@@@ -27,9 -27,15 +27,10 @@@ import org.apache.sis.util.Static
import org.apache.sis.util.UnconvertibleObjectException;
import org.apache.sis.util.collection.WeakHashSet;
import org.apache.sis.util.resources.Errors;
+ import org.apache.sis.setup.GeometryLibrary;
// Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.Operation;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.filter.Expression;
+import org.apache.sis.filter.Expression;
/**
@@@ -252,12 -250,8 +253,12 @@@ public final class FeatureOperations ex
*
* <h4>Read/write behavior</h4>
* This operation is read-only. Calls to {@code Attribute.setValue(Envelope)} will result in an
- * {@link IllegalStateException} to be thrown.
+ * {@link UnsupportedOperationException} to be thrown.
*
+ * <div class="warning"><b>Warning:</b>
+ * The type of {@code geometryAttributes} elements will be changed to {@code PropertyType}
+ * if and when such interface will be defined in GeoAPI.</div>
+ *
* @param identification the name and other information to be given to the operation.
* @param crs the Coordinate Reference System in which to express the envelope, or {@code null}.
* @param geometryAttributes the operation or attribute type from which to get geometry values.
@@@ -272,6 -266,44 +273,44 @@@
return POOL.unique(new EnvelopeOperation(identification, crs, geometryAttributes));
}
+ /**
+ * Creates a single geometry from a sequence of points or polylines stored in another property.
+ * When evaluated, this operation reads a feature property containing a sequence of {@code Point}s or {@code Polyline}s.
+ * Those geometries shall be instances of the specified geometry library (e.g. JTS or ESRI).
+ * The merged geometry is usually a {@code Polyline},
+ * unless the sequence of source geometries is empty or contains a single element.
+ * The merged geometry is re-computed every time that the operation is evaluated.
+ *
+ * <h4>Examples</h4>
+ * <p><i>Polylines created from points:</i>
+ * a boat that record it's position every hour.
+ * The input is a list of all positions stored in an attribute with [0 … ∞] multiplicity.
+ * This operation will extract each position and create a line as a new attribute.</p>
+ *
+ * <p><i>Polylines created from other polylines:</i>
+ * a boat that record track every hour.
+ * The input is a list of all tracks stored in an attribute with [0 … ∞] multiplicity.
+ * This operation will extract each track and create a polyline as a new attribute.</p>
+ *
+ * <h4>Read/write behavior</h4>
+ * This operation is read-only. Calls to {@code Attribute.setValue(…)}
+ * will result in an {@link UnsupportedOperationException} to be thrown.
+ *
+ * @param identification the name of the operation, together with optional information.
+ * @param library the library providing the implementations of geometry objects to read and write.
+ * @param components attribute, association or operation providing the geometries to group as a polyline.
+ * @return a feature operation which computes its values by merging points or polylines.
+ *
+ * @since 1.4
+ */
- public static Operation groupAsPolyline(final Map<String,?> identification, final GeometryLibrary library,
- final PropertyType components)
++ public static AbstractOperation groupAsPolyline(final Map<String,?> identification, final GeometryLibrary library,
++ final AbstractIdentifiedType components)
+ {
+ ArgumentChecks.ensureNonNull("library", library);
+ ArgumentChecks.ensureNonNull("components", components);
+ return POOL.unique(GroupAsPolylineOperation.create(identification, library, components));
+ }
+
/**
* Creates an operation which delegates the computation to a given expression.
* The {@code expression} argument should generally be an instance of
@@@ -286,12 -322,13 +329,13 @@@
*
* @since 1.4
*/
- public static <V> AbstractOperation expression(final Map<String,?> identification,
- final Function<AbstractFeature, ? extends V> expression,
- final DefaultAttributeType<? super V> result)
- public static <V> Operation function(final Map<String,?> identification,
- final Function<? super Feature, ? extends V> expression,
- final AttributeType<? super V> resultType)
++ public static <V> AbstractOperation function(final Map<String,?> identification,
++ final Function<? super AbstractFeature, ? extends V> expression,
++ final DefaultAttributeType<? super V> resultType)
{
ArgumentChecks.ensureNonNull("expression", expression);
- return POOL.unique(new ExpressionOperation<>(identification, expression, result));
+ ArgumentChecks.ensureNonNull("resultType", resultType);
+ return POOL.unique(ExpressionOperation.create(identification, expression, resultType));
}
/**
@@@ -310,10 -351,10 +358,10 @@@
*
* @since 1.4
*/
- public static <V> AbstractOperation expressionToResult(final Map<String,?> identification,
- final Expression<AbstractFeature, ?> expression,
- final DefaultAttributeType<V> result)
- public static <V> Operation expression(final Map<String,?> identification,
- final Expression<? super Feature, ?> expression,
- final AttributeType<V> resultType)
++ public static <V> AbstractOperation expression(final Map<String,?> identification,
++ final Expression<? super AbstractFeature, ?> expression,
++ final DefaultAttributeType<V> resultType)
{
- return expression(identification, expression.toValueType(result.getValueClass()), result);
+ return function(identification, expression.toValueType(resultType.getValueClass()), resultType);
}
}
diff --cc core/sis-feature/src/main/java/org/apache/sis/feature/Features.java
index becc02779f,cbf2f98185..a583ba3dd2
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/Features.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/Features.java
@@@ -126,14 -138,43 +126,43 @@@ public final class Features extends Sta
*
* @since 1.1
*/
+ @SuppressWarnings("unchecked")
- public static Optional<AttributeType<?>> toAttribute(IdentifiedType type) {
- return toIdentifiedType(type, (Class) AttributeType.class);
+ public static Optional<DefaultAttributeType<?>> toAttribute(AbstractIdentifiedType type) {
- if (!(type instanceof DefaultAttributeType<?>)) {
++ return toIdentifiedType(type, (Class) DefaultAttributeType.class);
+ }
+
+ /**
- * Returns the given type as a {@link FeatureAssociationRole} by casting if possible, or by getting the result type
++ * Returns the given type as a {@code FeatureAssociationRole} by casting if possible, or by getting the result type
+ * of an operation. More specifically this method returns the first of the following types which apply:
+ *
+ * <ul>
- * <li>If the given type is an instance of {@link FeatureAssociationRole}, then it is returned as-is.</li>
- * <li>If the given type is an instance of {@link Operation} and the {@linkplain Operation#getResult()
- * result type} is an {@link FeatureAssociationRole}, then that result type is returned.</li>
- * <li>If the given type is an instance of {@link Operation} and the {@linkplain Operation#getResult()
++ * <li>If the given type is an instance of {@code FeatureAssociationRole}, then it is returned as-is.</li>
++ * <li>If the given type is an instance of {@code Operation} and the {@code Operation.getResult()
++ * result type} is an {@code FeatureAssociationRole}, then that result type is returned.</li>
++ * <li>If the given type is an instance of {@code Operation} and the {@code Operation.getResult()
+ * result type} is another operation, then the above check is performed recursively.</li>
+ * </ul>
+ *
+ * @param type the data type to express as an attribute type.
+ * @return the association role, or empty if this method cannot find any.
+ *
+ * @since 1.4
+ */
- public static Optional<FeatureAssociationRole> toAssociation(IdentifiedType type) {
- return toIdentifiedType(type, FeatureAssociationRole.class);
++ public static Optional<DefaultAssociationRole> toAssociation(AbstractIdentifiedType type) {
++ return toIdentifiedType(type, DefaultAssociationRole.class);
+ }
+
+ /**
- * Implementation of {@link #toAttribute(IdentifiedType)} and {@link #toAssociation(IdentifiedType)}.
++ * Implementation of {@code toAttribute(IdentifiedType)} and {@code toAssociation(IdentifiedType)}.
+ */
- private static <T> Optional<T> toIdentifiedType(IdentifiedType type, final Class<T> target) {
++ private static <T> Optional<T> toIdentifiedType(AbstractIdentifiedType type, final Class<T> target) {
+ if (!target.isInstance(type)) {
- if (!(type instanceof Operation)) {
+ if (!(type instanceof AbstractOperation)) {
return Optional.empty();
}
- type = ((Operation) type).getResult();
+ type = ((AbstractOperation) type).getResult();
- if (!(type instanceof DefaultAttributeType<?>)) {
+ if (!target.isInstance(type)) {
- if (!(type instanceof Operation)) {
+ if (!(type instanceof AbstractOperation)) {
return Optional.empty();
}
/*
@@@ -141,9 -182,9 +170,9 @@@
* contain a cycle. However, given that the consequence of an infinite cycle here
* would be thread freeze, we check as a safety.
*/
- final Map<IdentifiedType,Boolean> done = new IdentityHashMap<>(4);
- while (!target.isInstance(type = ((Operation) type).getResult())) {
- if (!(type instanceof Operation) || done.put(type, Boolean.TRUE) != null) {
+ final Map<AbstractIdentifiedType,Boolean> done = new IdentityHashMap<>(4);
- while (!((type = ((AbstractOperation) type).getResult()) instanceof DefaultAttributeType<?>)) {
++ while (!target.isInstance(type = ((AbstractOperation) type).getResult())) {
+ if (!(type instanceof AbstractOperation) || done.put(type, Boolean.TRUE) != null) {
return Optional.empty();
}
}
diff --cc core/sis-feature/src/main/java/org/apache/sis/feature/GroupAsPolylineOperation.java
index 0000000000,59f515554d..d6e8b972fa
mode 000000,100644..100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/GroupAsPolylineOperation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/GroupAsPolylineOperation.java
@@@ -1,0 -1,250 +1,242 @@@
+ /*
+ * 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.feature;
+
+ import java.util.Map;
+ import java.util.Collection;
+ import java.util.Iterator;
+ import java.util.EnumMap;
+ import org.opengis.parameter.ParameterDescriptorGroup;
+ import org.opengis.parameter.ParameterValueGroup;
+ import org.apache.sis.internal.feature.AttributeConvention;
+ import org.apache.sis.internal.feature.FeatureUtilities;
+ import org.apache.sis.internal.feature.Geometries;
+ import org.apache.sis.internal.feature.GeometryWrapper;
+ import org.apache.sis.internal.feature.Resources;
+ import org.apache.sis.setup.GeometryLibrary;
+
-// Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.Property;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.Operation;
-
+
+ /**
+ * Creates a single (Multi){@code Polyline} instance from a sequence of points or polylines stored in another property.
+ * This is the implementation of {@link FeatureOperations#groupAsPolyline FeatureOperations.groupAsPolyline(…)}.
+ *
+ * @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.4
+ * @since 0.8
+ */
+ final class GroupAsPolylineOperation extends AbstractOperation {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = -1995248173704801739L;
+
+ /**
+ * The parameter descriptor for the "Group polylines" operation, which does not take any parameter.
+ */
+ private static final ParameterDescriptorGroup EMPTY_PARAMS = FeatureUtilities.parameters("GroupAsPolyline");
+
+ /**
+ * Name of the property to follow in order to get the geometries to add to a polyline.
+ * This property can be an attribute, operation or feature association,
+ * usually with [0 … ∞] multiplicity.
+ */
+ private final String propertyName;
+
+ /**
+ * Whether the property giving components is an association to feature instances.
+ */
+ private final boolean isFeatureAssociation;
+
+ /**
+ * The geometry library.
+ */
+ private final Geometries<?> geometries;
+
+ /**
+ * The {@link #resultType} for each library, created when first needed.
+ * Used for sharing the same instance for all operations using the same library.
+ */
+ private static final EnumMap<GeometryLibrary, DefaultAttributeType<?>> TYPES = new EnumMap<>(GeometryLibrary.class);
+
+ /**
+ * Returns an operation which will group into a single geometry all geometries contained in the specified property.
+ *
+ * @param identification the name of the operation, together with optional information.
+ * @param library the library providing the implementations of geometry objects to read and write.
+ * @param components attribute, association or operation providing the geometries to group as a polyline.
+ */
- static Operation create(final Map<String,?> identification, final GeometryLibrary library, PropertyType components) {
++ static AbstractOperation create(final Map<String,?> identification, final GeometryLibrary library, AbstractIdentifiedType components) {
+ if (components instanceof LinkOperation) {
+ components = ((LinkOperation) components).result;
+ }
+ final boolean isFeatureAssociation;
- if (components instanceof AttributeType<?>) {
- if (((AttributeType<?>) components).getMaximumOccurs() <= 1) {
++ if (components instanceof DefaultAttributeType<?>) {
++ if (((DefaultAttributeType<?>) components).getMaximumOccurs() <= 1) {
+ return new LinkOperation(identification, components);
+ }
+ isFeatureAssociation = false;
+ } else {
- isFeatureAssociation = (components instanceof FeatureAssociationRole)
- && ((FeatureAssociationRole) components).getMaximumOccurs() == 1;
++ isFeatureAssociation = (components instanceof DefaultAssociationRole)
++ && ((DefaultAssociationRole) components).getMaximumOccurs() == 1;
+ if (!isFeatureAssociation) {
+ throw new IllegalArgumentException(Resources.format(Resources.Keys.IllegalPropertyType_2,
+ components.getName(), components.getClass()));
+ }
+ }
+ return new GroupAsPolylineOperation(identification, Geometries.implementation(library), components, isFeatureAssociation);
+ }
+
+ /**
+ * Creates an operation which will group into a single polyline all geometries contained in the specified property.
+ * This constructor shall be invoked only after the {@code source} is known to contain collection, i.e. the maximum
+ * number of occurrences of attribute values or feature instances is greater than 1.
+ */
+ private GroupAsPolylineOperation(final Map<String,?> identification, final Geometries<?> geometries,
- final PropertyType components, final boolean isFeatureAssociation)
++ final AbstractIdentifiedType components, final boolean isFeatureAssociation)
+ {
+ super(identification);
+ this.geometries = geometries;
+ this.propertyName = components.getName().toString();
+ this.isFeatureAssociation = isFeatureAssociation;
+ }
+
+ /**
+ * Returns an empty parameter descriptor group.
+ */
+ @Override
+ public ParameterDescriptorGroup getParameters() {
+ return EMPTY_PARAMS;
+ }
+
+ /**
+ * Returns the expected result type.
+ */
+ @Override
- public final AttributeType<?> getResult() {
++ public final DefaultAttributeType<?> getResult() {
+ synchronized (TYPES) {
+ return TYPES.computeIfAbsent(geometries.library, (library) -> {
+ var name = Map.of(AbstractIdentifiedType.NAME_KEY, AttributeConvention.ENVELOPE_PROPERTY);
+ return new DefaultAttributeType<>(name, geometries.polylineClass, 1, 1, null);
+ });
+ }
+ }
+
+ /**
+ * Executes the operation on the specified feature.
+ */
+ @Override
+ @SuppressWarnings({"rawtypes", "unchecked"})
- public final Property apply(Feature feature, ParameterValueGroup parameters) {
++ public final Property apply(AbstractFeature feature, ParameterValueGroup parameters) {
+ return new Result<>(getResult(), feature);
+ }
+
+
+ /**
+ * The attribute resulting from execution of the {@link GroupAsPolylineOperation}.
+ * The value is computed when first requested, then cached for this {@code Result} instance only.
- * Note that the cache is not used when {@link #apply(Feature, ParameterValueGroup)} is invoked,
++ * Note that the cache is not used when {@code apply(Feature, ParameterValueGroup)} is invoked,
+ * causing a new value to be computed again. The intent is to behave as if the operation has been
+ * executed at {@code apply(…)} invocation time, even if we deferred the actual execution.
+ *
+ * @param <G> the root geometry class (implementation-dependent).
+ */
+ private final class Result<G> extends OperationResult<G> {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = 5558751012506417903L;
+
+ /**
+ * The result, computed when first needed.
+ */
+ private transient G geometry;
+
+ /**
+ * Creates a new result for an execution on the given feature.
+ * The actual computation is deferred to the first call of {@link #getValue()}.
+ */
- Result(final AttributeType<G> resultType, final Feature feature) {
++ Result(final DefaultAttributeType<G> resultType, final AbstractFeature feature) {
+ super(resultType, feature);
+ }
+
+ /**
+ * Computes the geometry from all points or polylines found in the associated feature.
+ *
+ * @throws ClassCastException if a feature, a property value or a geometry is not of the expected class.
+ */
+ @Override
+ public G getValue() {
+ if (geometry == null) {
+ geometry = compute();
+ }
+ return geometry;
+ }
+
+ /**
+ * Computes the geometry when first needed.
+ */
+ private G compute() {
+ /*
+ * Cast to `Collection` should be safe if the constructor
+ * ensured that `Features.getMaximumOccurs(property) > 1`.
+ */
+ Iterator<?> paths = ((Collection<?>) feature.getPropertyValue(propertyName)).iterator();
+ if (isFeatureAssociation) {
+ final Iterator<?> it = paths;
+ paths = new Iterator<Object>() {
+ @Override public boolean hasNext() {
+ return it.hasNext();
+ }
+
+ @Override public Object next() {
- return ((Feature) it.next()).getPropertyValue(AttributeConvention.GEOMETRY);
++ return ((AbstractFeature) it.next()).getPropertyValue(AttributeConvention.GEOMETRY);
+ }
+ };
+ }
+ while (paths.hasNext()) {
+ GeometryWrapper<?> first = geometries.castOrWrap(paths.next());
+ if (first != null) {
+ final Object geom = first.mergePolylines(paths);
+ return getType().getValueClass().cast(geom);
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Computes a hash-code value for this operation.
+ */
+ @Override
+ public int hashCode() {
+ return super.hashCode() + propertyName.hashCode() + geometries.hashCode();
+ }
+
+ /**
+ * Compares this operation with the given object for equality.
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (super.equals(obj)) {
+ final GroupAsPolylineOperation that = (GroupAsPolylineOperation) obj;
+ return propertyName.equals(that.propertyName) &&
+ geometries.equals(that.geometries);
+ }
+ return false;
+ }
+ }
diff --cc core/sis-feature/src/main/java/org/apache/sis/feature/LinkOperation.java
index fde190b24d,d195ebe596..90d6ac766c
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/LinkOperation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/LinkOperation.java
@@@ -48,7 -54,8 +48,7 @@@ final class LinkOperation extends Abstr
/**
* The type of the result.
*/
- private final AbstractIdentifiedType result;
- @SuppressWarnings("serial") // Most SIS implementations are serializable.
- final PropertyType result;
++ final AbstractIdentifiedType result;
/**
* The name of the referenced attribute or feature association.
diff --cc core/sis-feature/src/main/java/org/apache/sis/feature/OperationResult.java
index 0000000000,b05f884c54..130ffe453d
mode 000000,100644..100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/OperationResult.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/OperationResult.java
@@@ -1,0 -1,71 +1,66 @@@
+ /*
+ * 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.feature;
+
+ import org.apache.sis.util.resources.Errors;
+
-// Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-
+
+ /**
+ * Base class of attributes that are the result of a feature operation.
+ * This base class is defined for making easier to identify where computations are done.
+ *
+ * @todo A future version may provide caching services, methods for taking a snapshot, <i>etc.</i>
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.4
+ *
+ * @param <V> the type of attribute values.
+ *
+ * @since 1.4
+ */
+ abstract class OperationResult<V> extends AbstractAttribute<V> {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = 1418917854672134381L;
+
+ /**
+ * The feature instance to use as a source for computing the result.
+ */
+ @SuppressWarnings("serial") // Most SIS implementations are serializable.
- protected final Feature feature;
++ protected final AbstractFeature feature;
+
+ /**
+ * Creates a new operation for a result of the given type.
+ *
+ * @param type information about the attribute (base Java class, domain of values, <i>etc.</i>).
+ */
- protected OperationResult(final AttributeType<V> type, final Feature feature) {
++ protected OperationResult(final DefaultAttributeType<V> type, final AbstractFeature feature) {
+ super(type);
+ this.feature = feature;
+ }
+
+ /**
+ * Retro-propagate an operation result to the properties in the source feature instance.
+ * This is an optional operation.
+ * The default implementation unconditionally throws an {@link UnsupportedOperationException}.
+ */
+ @Override
+ public void setValue(V value) {
- throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, Attribute.class));
++ throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, AbstractAttribute.class));
+ }
+ }
diff --cc core/sis-feature/src/main/java/org/apache/sis/feature/StringJoinOperation.java
index 09b83ae978,3bc72ab684..8c9ce69271
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/StringJoinOperation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/StringJoinOperation.java
@@@ -318,9 -326,8 +313,8 @@@ final class StringJoinOperation extend
/**
* Creates a new attribute for the given feature.
*/
- Result(final Feature feature) {
+ Result(final AbstractFeature feature) {
- super(resultType);
- this.feature = feature;
+ super(resultType, feature);
}
/**
diff --cc core/sis-feature/src/main/java/org/apache/sis/filter/AssociationValue.java
index a3f20b3055,49cdb7f41e..f72ddebc54
--- 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,15 -25,16 +25,16 @@@ 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;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.PropertyNotFoundException;
-import org.opengis.filter.Expression;
-import org.opengis.filter.ValueReference;
+import org.opengis.util.ScopedName;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.DefaultAssociationRole;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.internal.geoapi.filter.Name;
+import org.apache.sis.internal.geoapi.filter.ValueReference;
/**
@@@ -103,10 -99,21 +104,21 @@@ final class AssociationValue<V> extend
* Returns the class of resources expected by this expression.
*/
@Override
- public final Class<Feature> getResourceClass() {
- return Feature.class;
+ public final Class<AbstractFeature> getResourceClass() {
+ return AbstractFeature.class;
}
+ /**
+ * 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.
+ * This method does not inherit directly the properties of the {@linkplain #accessor} because it
+ * does not operate on the same resource, so the non-transitive function properties may not hold.
+ */
+ @Override
+ public Set<FunctionProperty> properties() {
+ return transitiveProperties(accessor.getParameters());
+ }
+
/**
* For {@link #toString()} implementation.
*/
diff --cc core/sis-feature/src/main/java/org/apache/sis/filter/LeafExpression.java
index aac4113826,c7801d8f02..4a0f212dc0
--- 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
@@@ -29,10 -30,12 +30,11 @@@ import org.apache.sis.internal.feature.
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;
-import org.opengis.feature.AttributeType;
-import org.opengis.filter.Expression;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.DefaultAttributeType;
/**
@@@ -79,7 -92,11 +91,11 @@@ abstract class LeafExpression<R,V> exte
* @param <R> the type of resources used as inputs.
* @param <V> the type of value computed by the expression.
*/
- static class Literal<R,V> extends LeafExpression<R,V> implements org.opengis.filter.Literal<R,V> {
+ static class Literal<R,V> extends LeafExpression<R,V> implements org.apache.sis.internal.geoapi.filter.Literal<R,V> {
+ /** The properties of this function, which returns constants. */
+ private static final Set<FunctionProperty> CONSTANT =
+ Set.of(FunctionProperty.ORDER_PRESERVING, FunctionProperty.ORDER_REVERSING);
+
/** For cross-version compatibility. */
private static final long serialVersionUID = -8383113218490957822L;
diff --cc core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureExpression.java
index bc345fff42,34acd54f98..677c6e1559
--- 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,20 +16,21 @@@
*/
package org.apache.sis.internal.feature;
- import org.apache.sis.filter.Expression;
+ 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.feature.DefaultFeatureType;
+import org.apache.sis.internal.geoapi.filter.Literal;
+import org.apache.sis.internal.geoapi.filter.ValueReference;
+ import org.apache.sis.internal.filter.Node;
+
++// Branch-dependent imports
++import org.apache.sis.filter.Expression;
+
/**
* OGC expressions or other functions operating on feature instances.
@@@ -36,9 -40,9 +41,9 @@@
*
* @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 <R> the type of resources (e.g. {@code Feature}) used as inputs.
* @param <V> the type of values computed by the expression.
*
* @since 1.0
diff --cc core/sis-feature/src/main/java/org/apache/sis/internal/filter/Node.java
index 5e615957e1,0dedb78d65..8c2ccd3bdc
--- 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
@@@ -217,9 -222,93 +220,93 @@@ public abstract class Node implements S
if (expression instanceof GeometryConverter<?,?>) {
return ((GeometryConverter<?,G>) expression).library;
}
- throw new InvalidFilterValueException(Resources.format(Resources.Keys.NotAGeometryAtFirstExpression));
+ throw new IllegalArgumentException(Resources.format(Resources.Keys.NotAGeometryAtFirstExpression));
}
+ /**
+ * The set of all properties that may be present in {@link FeatureExpression#properties()}
+ * when the only available information is the list of parameters. When we do not know how
+ * an expression is using the parameters, the function properties should be the empty set.
+ * When combining a function properties with the properties inherited from the parameters,
+ * the only properties that can be added to an initially empty set are the properties that
+ * are {@linkplain FunctionProperty#concatenate(Set, Set) concatenated} with the logical
+ * {@code OR} operation. In the current {@link FunctionProperty} enumeration, the only
+ * property handled that way is {@code VOLATILE}.
+ */
+ private static final Set<FunctionProperty> TRANSITIVE_PROPERTIES = Set.of(FunctionProperty.VOLATILE);
+
+ /**
+ * Whether the given set of properties contains the {@link #TRANSITIVE_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 #TRANSITIVE_PROPERTIES}.
+ * This method assumes that {@code TRANSITIVE_PROPERTIES} is a singleton and that the property can be combined
+ * by a logical {@code OR} operation.
+ *
+ * @param operands the expressions from which to get the function properties.
+ * @return whether is present the single function property that may appear.
+ */
+ 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.
+ * This method delegates to {@link FeatureExpression#properties()} if possible.
+ * Otherwise this method assumes that the intrinsic properties of the given expression are unknown,
+ * and inherits from the parameters only the properties that can be added to an initially empty set.
+ *
+ * @param function the expression for which to query function properties, or {@code null}.
+ * @return the manners in which values are computed from resources.
+ */
+ public static Set<FunctionProperty> properties(final Expression<?,?> function) {
+ if (function instanceof FeatureExpression<?,?>) {
+ return ((FeatureExpression<?,?>) function).properties();
+ } else if (function != null) {
+ return transitiveProperties(function.getParameters());
+ } else {
+ return Set.of();
+ }
+ }
+
+ /**
+ * Returns the manner in which values are computed from resources in an expression having the given operands.
+ * This method assumes that the intrinsic properties of the parent expression or parent filter are unknown,
+ * and inherits from the operands only the properties that can be added to an initially empty set.
+ *
+ * <p>Note that {@code transitiveProperties(function.getParameters())} is <strong>not</strong> equivalent to
+ * {@code properties(function)}. It is rather equivalent to the following code, where the parent expression
+ * is not the final step of a chain of operations, and the next step has no known properties:</p>
+ *
+ * {@snippet lang="java" :
+ * FunctionProperty.concatenate(transitiveProperties(operands), Set.of());
+ * }
+ *
+ * @param <R> the type of resources.
+ * @param operands the operands from which to inherit function properties.
+ * @return the manners in which values are computed from resources.
+ */
+ @SuppressWarnings("ReturnOfCollectionOrArrayField") // Because immutable.
+ public static <R> Set<FunctionProperty> transitiveProperties(final Iterable<Expression<R,?>> operands) {
+ return isVolatile(operands) ? TRANSITIVE_PROPERTIES : 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 --cc core/sis-feature/src/test/java/org/apache/sis/feature/GroupAsPolylineOperationTest.java
index 0000000000,e3285521d3..62255e450a
mode 000000,100644..100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/GroupAsPolylineOperationTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/GroupAsPolylineOperationTest.java
@@@ -1,0 -1,71 +1,65 @@@
+ /*
+ * 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.feature;
+
+ import java.util.Map;
+ import java.util.Arrays;
+ import com.esri.core.geometry.Point;
+ import com.esri.core.geometry.Polyline;
+ import org.apache.sis.feature.builder.FeatureTypeBuilder;
+ import org.apache.sis.setup.GeometryLibrary;
+
+ // Test dependencies
+ import org.apache.sis.test.TestCase;
+ import org.junit.Test;
+
+ import static org.junit.Assert.*;
+
-// Branch-dependent imports
-import org.opengis.feature.Attribute;
-import org.opengis.feature.Feature;
-import org.opengis.feature.Operation;
-import org.opengis.feature.Property;
-
+
+ /**
+ * Tests {@link GroupAsPolylineOperation}.
+ *
+ * @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.4
+ * @since 0.4
+ */
+ public final class GroupAsPolylineOperationTest extends TestCase {
+ /**
+ * Tests a feature with a sequence of points.
+ */
+ @Test
+ public void testPoints() {
+ final FeatureTypeBuilder builder = new FeatureTypeBuilder().setName("test");
+ builder.addAttribute(Point.class).setMaximumOccurs(10).setName("points");
- final Feature feature = builder.build().newInstance();
++ final AbstractFeature feature = builder.build().newInstance();
+ feature.setPropertyValue("points", Arrays.asList(
+ new Point(-6, 4),
+ new Point(12, 7),
+ new Point( 8, 6)));
+
- final Operation group = FeatureOperations.groupAsPolyline(Map.of("name", "polyline"),
++ final AbstractOperation group = FeatureOperations.groupAsPolyline(Map.of("name", "polyline"),
+ GeometryLibrary.ESRI, feature.getType().getProperty("points"));
+
- final Property result = group.apply(feature, null);
- final Object value = ((Attribute<?>) result).getValue();
++ final var result = group.apply(feature, null);
++ final Object value = ((AbstractAttribute<?>) result).getValue();
+ final Polyline poly = (Polyline) value;
+ assertEquals(-6, poly.getPoint(0).getX(), STRICT);
+ assertEquals( 7, poly.getPoint(1).getY(), STRICT);
+ assertEquals( 8, poly.getPoint(2).getX(), STRICT);
+ }
+ }
diff --cc core/sis-feature/src/test/java/org/apache/sis/filter/LogicalFilterTest.java
index 6cadd74277,8555b04741..a28a3dec78
--- 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
@@@ -97,6 -103,21 +99,21 @@@ public final class LogicalFilterTest ex
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") {
++ final var literal = new LeafExpression.Literal<AbstractFeature,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);
++ final Filter<AbstractFeature> operand = factory.isNull(literal);
++ final Filter<AbstractFeature> filter = factory.not(operand);
+ assertTrue(isVolatile(filter));
+ }
+
/**
* Implementation of {@link #testAnd()} and {@link #testOr()}.
*
diff --cc storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureQuery.java
index a95ce19519,1d34f86d1d..c4b1749677
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureQuery.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureQuery.java
@@@ -112,7 -118,7 +114,7 @@@ public class FeatureQuery extends Quer
* @see #setSelection(Filter)
*/
@SuppressWarnings("serial") // Most SIS implementations are serializable.
- private Filter<AbstractFeature> selection;
- private Filter<? super Feature> selection;
++ private Filter<? super AbstractFeature> selection;
/**
* The number of feature instances to skip from the beginning.
@@@ -193,12 -199,12 +195,12 @@@
* @throws IllegalArgumentException if a property is duplicated.
*/
@SafeVarargs
- public final void setProjection(final Expression<AbstractFeature, ?>... properties) {
- public final void setProjection(final Expression<? super Feature, ?>... properties) {
++ public final void setProjection(final Expression<? super AbstractFeature, ?>... properties) {
NamedExpression[] wrappers = null;
if (properties != null) {
wrappers = new NamedExpression[properties.length];
for (int i=0; i<wrappers.length; i++) {
- final Expression<AbstractFeature, ?> e = properties[i];
- final Expression<? super Feature, ?> e = properties[i];
++ final Expression<? super AbstractFeature, ?> e = properties[i];
ArgumentChecks.ensureNonNullElement("properties", i, e);
wrappers[i] = new NamedExpression(e);
}
@@@ -274,7 -299,7 +295,7 @@@
*
* @param selection the filter, or {@code null} if none.
*/
- public void setSelection(final Filter<AbstractFeature> selection) {
- public void setSelection(final Filter<? super Feature> selection) {
++ public void setSelection(final Filter<? super AbstractFeature> selection) {
this.selection = selection;
}
@@@ -285,7 -310,7 +306,7 @@@
*
* @return the filter, or {@code null} if none.
*/
- public Filter<AbstractFeature> getSelection() {
- public Filter<? super Feature> getSelection() {
++ public Filter<? super AbstractFeature> getSelection() {
return selection;
}
@@@ -485,7 -533,7 +535,7 @@@
* Never {@code null}.
*/
@SuppressWarnings("serial")
- public final Expression<AbstractFeature,?> expression;
- public final Expression<? super Feature, ?> expression;
++ public final Expression<? super AbstractFeature, ?> expression;
/**
* The name to assign to the expression result, or {@code null} if unspecified.
@@@ -508,7 -556,7 +558,7 @@@
*
* @param expression the literal, value reference or expression to be retrieved by a {@code Query}.
*/
- public NamedExpression(final Expression<AbstractFeature,?> expression) {
- public NamedExpression(final Expression<? super Feature, ?> expression) {
++ public NamedExpression(final Expression<? super AbstractFeature, ?> expression) {
this(expression, (GenericName) null);
}
@@@ -518,7 -566,7 +568,7 @@@
* @param expression the literal, value reference or expression to be retrieved by a {@code Query}.
* @param alias the name to assign to the expression result, or {@code null} if unspecified.
*/
- public NamedExpression(final Expression<AbstractFeature,?> expression, final GenericName alias) {
- public NamedExpression(final Expression<? super Feature,?> expression, final GenericName alias) {
++ public NamedExpression(final Expression<? super AbstractFeature,?> expression, final GenericName alias) {
this(expression, alias, ProjectionType.STORED);
}
@@@ -529,7 -577,7 +579,7 @@@
* @param expression the literal, value reference or expression to be retrieved by a {@code Query}.
* @param alias the name to assign to the expression result, or {@code null} if unspecified.
*/
- public NamedExpression(final Expression<AbstractFeature,?> expression, final String alias) {
- public NamedExpression(final Expression<? super Feature,?> expression, final String alias) {
++ public NamedExpression(final Expression<? super AbstractFeature,?> expression, final String alias) {
ArgumentChecks.ensureNonNull("expression", expression);
this.expression = expression;
this.alias = (alias != null) ? Names.createLocalName(null, null, alias) : null;
@@@ -545,7 -593,7 +595,7 @@@
*
* @since 1.4
*/
- public NamedExpression(final Expression<AbstractFeature,?> expression, final GenericName alias, ProjectionType type) {
- public NamedExpression(final Expression<? super Feature,?> expression, final GenericName alias, ProjectionType type) {
++ public NamedExpression(final Expression<? super AbstractFeature,?> expression, final GenericName alias, ProjectionType type) {
ArgumentChecks.ensureNonNull("expression", expression);
ArgumentChecks.ensureNonNull("type", type);
this.expression = expression;
@@@ -689,7 -739,7 +741,7 @@@
* For each property, get the expected type (mandatory) and its name (optional).
* A default name will be computed if no alias were explicitly given by user.
*/
- final Expression<AbstractFeature,?> expression = item.expression;
- final Expression<? super Feature,?> expression = item.expression;
++ final Expression<? super AbstractFeature,?> expression = item.expression;
final FeatureExpression<?,?> fex = FeatureExpression.castOrCopy(expression);
final PropertyTypeBuilder resultType;
if (fex == null || (resultType = fex.expectedType(valueType, ftb)) == null) {
@@@ -739,18 -789,20 +791,20 @@@
} while (!names.add(text.toString()));
name = Names.createLocalName(null, null, text);
}
- resultType.setName(name);
/*
- * If the attribute that we just added should be virtual,
- * replace the attribute by an operation.
+ * If the attribute that we just added should be virtual, replace the attribute by an operation.
+ * We need to keep the property name computed by `fex.expectedType(…)` for the operation result,
+ * because that name is the name of the link to create if the operation is `ValueReference`.
*/
- if (item.type == ProjectionType.VIRTUAL && resultType instanceof AttributeTypeBuilder<?>) {
+ if (item.type == ProjectionType.COMPUTING && resultType instanceof AttributeTypeBuilder<?>) {
final var ab = (AttributeTypeBuilder<?>) resultType;
- final AttributeType<?> storedType = ab.build();
+ final DefaultAttributeType<?> storedType = ab.build();
if (ftb.properties().remove(resultType)) {
final var properties = Map.of(AbstractOperation.NAME_KEY, name);
- ftb.addProperty(FeatureOperations.expressionToResult(properties, expression, storedType));
+ ftb.addProperty(FeatureOperations.expression(properties, expression, storedType));
}
+ } else {
+ resultType.setName(name);
}
}
return ftb.build();
diff --cc storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureSubset.java
index 5a1b87e95d,5b2e67c900..825fecfaff
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureSubset.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureSubset.java
@@@ -110,7 -110,7 +110,7 @@@ final class FeatureSubset extends Abstr
/*
* Apply filter.
*/
- final Filter<AbstractFeature> selection = query.getSelection();
- final Filter<? super Feature> selection = query.getSelection();
++ final Filter<? super AbstractFeature> selection = query.getSelection();
if (selection != null && !selection.equals(Filter.include())) {
stream = stream.filter(selection);
}
@@@ -139,10 -139,10 +139,10 @@@
* Transform feature instances.
* Note: "projection" here is in relational database sense, not map projection.
*/
- final FeatureQuery.NamedExpression[] projection = query.getProjection();
+ final FeatureQuery.NamedExpression[] projection = query.getStoredProjection();
if (projection != null) {
@SuppressWarnings({"unchecked", "rawtypes"})
- final Expression<AbstractFeature,?>[] expressions = new Expression[projection.length];
- final Expression<? super Feature,?>[] expressions = new Expression[projection.length];
++ final Expression<? super AbstractFeature,?>[] expressions = new Expression[projection.length];
for (int i=0; i<expressions.length; i++) {
expressions[i] = projection[i].expression;
}
diff --cc storage/sis-storage/src/test/java/org/apache/sis/storage/FeatureQueryTest.java
index 04073e0a50,32ce1bd5fd..6fbf0b8c80
--- a/storage/sis-storage/src/test/java/org/apache/sis/storage/FeatureQueryTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/storage/FeatureQueryTest.java
@@@ -308,8 -328,8 +309,8 @@@ public final class FeatureQueryTest ext
/**
* Shortcut for creating expression for a projection computed on-the-fly.
*/
- private static FeatureQuery.NamedExpression virtualProjection(final Expression<Feature, ?> expression, final String alias) {
+ private static FeatureQuery.NamedExpression virtualProjection(final Expression<AbstractFeature, ?> expression, final String alias) {
- return new FeatureQuery.NamedExpression(expression, Names.createLocalName(null, null, alias), FeatureQuery.ProjectionType.VIRTUAL);
+ return new FeatureQuery.NamedExpression(expression, Names.createLocalName(null, null, alias), FeatureQuery.ProjectionType.COMPUTING);
}
/**
diff --cc storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Types.java
index 195e9bbace,c552af357d..c8e39408af
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Types.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Types.java
@@@ -47,8 -47,8 +47,8 @@@ import org.apache.sis.util.ResourceInte
import org.apache.sis.util.iso.DefaultNameFactory;
// Branch-dependent imports
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.Operation;
++import org.apache.sis.feature.AbstractOperation;
+import org.apache.sis.feature.DefaultFeatureType;
- import org.apache.sis.feature.DefaultAttributeType;
/**
@@@ -221,8 -221,7 +221,7 @@@ final class Types
* │ rtept │ WayPoint │ gpx:wptType │ [0 … ∞] │
* └────────────────┴────────────────┴───────────────────────┴──────────────┘
*/
- final DefaultAttributeType<?> groupResult = GroupAsPolylineOperation.getResult(geometries);
- GroupAsPolylineOperation groupOp = new GroupAsPolylineOperation(geomInfo, Tags.ROUTE_POINTS, groupResult);
- Operation groupOp = groupAsPolyline(geomInfo, Tags.ROUTE_POINTS, wayPoint);
++ AbstractOperation groupOp = groupAsPolyline(geomInfo, Tags.ROUTE_POINTS, wayPoint);
builder.clear().setSuperTypes(parent).setNameSpace(Tags.PREFIX).setName("Route");
builder.addProperty(groupOp);
builder.addProperty(FeatureOperations.envelope(envpInfo, null, groupOp));
@@@ -317,4 -316,16 +316,16 @@@
}
return builder.build();
}
+
+ /**
+ * Creates a new operation which will group the geometries in the given property into a single polyline.
+ *
+ * @param geomInfo the name of the operation, together with optional information.
+ * @param components name of the property providing the geometries to group as a polyline.
+ * @param type type of the property identified by {@code components}.
+ */
- private Operation groupAsPolyline(final Map<String,?> geomInfo, final String components, final FeatureType type) {
++ private AbstractOperation groupAsPolyline(final Map<String,?> geomInfo, final String components, final DefaultFeatureType type) {
+ var c = new DefaultAssociationRole(Map.of(DefaultAssociationRole.NAME_KEY, components), type, 1, 1);
+ return FeatureOperations.groupAsPolyline(geomInfo, geometries.library, c);
+ }
}