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 2025/05/21 16:17:37 UTC
(sis) branch main updated (ac4ad3f36a -> 3d377745a9)
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a change to branch main
in repository https://gitbox.apache.org/repos/asf/sis.git
from ac4ad3f36a Merge of automatic reorganization of imports order.
add ec8d89adde Allow to read tiles through HTTP using HTTP range requests. It requires some reorganization for allowing to know the ranges in advance.
add e07452d79e Check HTTP status when opening a file from an URL.
add 9a1e96040c Workaround for limitations observed in DuckDB 1.2.1: - Ignore the catalog name when the database is DuckDB. - Do not add SRID argument in geometries because not supported.
add ab0610bcf7 Remove the hack for DuckDB 1.2.1 since it has been fixed in DuckDB 1.2.2.0. Document DuckDB 1.2.2.0 as the minimal version required by Apache SIS.
add 8abf1fdeeb Add explicit `ST_AsBinary` or `ST_AsText` functions when requesting a geometry.
add bce9be08df Add a fallback for trying to guess the SRID when the table of a column is unknown. This is a workaround for incomplete JDBC drivers (in this case, DuckDB 1.2.2.0).
add aefe5d2936 Add getContentPath() method on Tile to allow access to the raw tile content without opening a resource
add 0b665692dc Simplify the default implementation of `getContentPath()`. Add `@since 1.5` Javadoc tag and adjust `{@link}` versus `{@linkplain}`. Minor editions to the Javadoc sentences and a bit of formatting (one sentence per line).
add 90aaa358f1 Clarification about the circumstances when `guessCRS` may be invoked.
add 2f1112858c Implement getFileSet() on landsat resources
add 12d68ec5d1 Resolve shapefile sibling files without going trought parent path which may be null
add db6cf1093b More reliable determination of the path to the metadata file used for opening a Landsat store. Opportunistic minor editions.
add 71c5b4f09b Add a `Features.getLinkTargets(PropertyType)` method in complement to `getLinkTarget(PropertyType)` method (plural versus singular). Bug fix in `SQLStore` where foreigner keys in queries were followed in the wrong directions (foreigner keys in tables were okay). Minor simplification in exception handling of `DeferredStream` and minor javadoc editions.
add 428ee02c5e Add a `PropertyTypeBuilder.replaceBy(…)` method.
add 0db3b21989 `SQLStore` should accept queries with a "projection" (in SQL sense) that contains links such as "sis:identifier".
add ff4f8725a6 Add a test for the new support of "sis:identifier" in FeatureQuery.
add 26bdb313f1 Move flexibility for configuring the builder wrapped by `FeatureProjectionBuilder.Item`. More conservative check of whether the FeatureType is different than the source.
add 305ae17810 Improve AWT to JTS conversion logic.
add ffa96dbde4 Merge branch 'geoapi-4.0' into geoapi-3.1. Contains improvements in `SQLStore`.
new 3d377745a9 Merge branch 'geoapi-3.1'. Contains improvements in: `SQLStore`, ShapeFile (incubator), conversions from AWT shapes to JTS, partial DuckDB support, and use of HTTP ranges in GeoHEIF.
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:
.../apache/sis/cloud/aws/s3/ClientFileSystem.java | 2 +-
.../org.apache.sis.feature/main/module-info.java | 1 +
.../org/apache/sis/feature/AbstractOperation.java | 4 +-
.../apache/sis/feature/ExpressionOperation.java | 2 +-
.../org/apache/sis/feature/FeatureOperations.java | 77 ++-
.../main/org/apache/sis/feature/Features.java | 25 +-
.../apache/sis/feature/StringJoinOperation.java | 14 +
.../sis/feature/builder/AttributeTypeBuilder.java | 14 +-
.../sis/feature/builder/FeatureTypeBuilder.java | 10 +-
.../sis/feature/builder/OperationWrapper.java | 30 +-
.../sis/feature/builder/PropertyTypeBuilder.java | 32 +-
.../apache/sis/feature/builder/TypeBuilder.java | 2 +-
.../org/apache/sis/feature/internal/Resources.java | 5 +
.../sis/feature/internal/Resources.properties | 1 +
.../sis/feature/internal/Resources_fr.properties | 1 +
.../main/org/apache/sis/feature/package-info.java | 2 +-
.../sis/feature/privy/FeatureExpression.java | 83 ++-
.../sis/feature/privy/FeatureProjection.java | 341 ++++++++++
.../feature/privy/FeatureProjectionBuilder.java | 739 +++++++++++++++++++++
.../org/apache/sis/feature/privy/FeatureView.java | 129 ++++
.../org/apache/sis/filter/ArithmeticFunction.java | 8 +-
.../org/apache/sis/filter/AssociationValue.java | 22 +-
.../org/apache/sis/filter/ConvertFunction.java | 21 +-
.../apache/sis/filter/DefaultFilterFactory.java | 26 +-
.../InvalidXPathException.java} | 38 +-
.../main/org/apache/sis/filter/LeafExpression.java | 15 +-
.../main/org/apache/sis/filter/PropertyValue.java | 72 +-
.../main/org/apache/sis/filter/privy/XPath.java | 18 +-
.../apache/sis/filter/sqlmm/FunctionWithSRID.java | 24 +-
.../apache/sis/filter/sqlmm/SpatialFunction.java | 36 +-
.../sis/geometry/wrapper/jts/ConverterTo2D.java | 227 +++++++
.../sis/geometry/wrapper/jts/ShapeConverter.java | 73 +-
.../feature/builder/FeatureTypeBuilderTest.java | 29 +
.../geometry/wrapper/jts/ShapeConverterTest.java | 40 +-
.../org/apache/sis/metadata/sql/privy/Dialect.java | 65 +-
.../apache/sis/metadata/sql/privy/Reflection.java | 2 +
.../apache/sis/metadata/sql/privy/Supports.java | 15 +
.../main/org/apache/sis/storage/landsat/Band.java | 27 +-
.../org/apache/sis/storage/landsat/BandGroup.java | 15 +
.../apache/sis/storage/landsat/LandsatStore.java | 27 +-
.../org/apache/sis/storage/sql/duckdb/DuckDB.java | 2 +-
.../sis/storage/sql/duckdb/package-info.java | 4 +
.../apache/sis/storage/sql/feature/Analyzer.java | 7 +-
.../org/apache/sis/storage/sql/feature/Column.java | 50 +-
.../apache/sis/storage/sql/feature/Database.java | 56 +-
.../sis/storage/sql/feature/FeatureAdapter.java | 20 +-
.../sis/storage/sql/feature/FeatureIterator.java | 31 +-
.../sis/storage/sql/feature/FeatureStream.java | 38 +-
.../sis/storage/sql/feature/GeometryEncoding.java | 93 ++-
.../storage/sql/feature/GeometryTypeEncoding.java | 2 +
.../sis/storage/sql/feature/InfoStatements.java | 124 +++-
.../sis/storage/sql/feature/QueryAnalyzer.java | 7 +-
.../sis/storage/sql/feature/SelectionClause.java | 10 +-
.../storage/sql/feature/SelectionClauseWriter.java | 10 +-
.../sis/storage/sql/feature/SpatialSchema.java | 9 +-
.../org/apache/sis/storage/sql/feature/Table.java | 48 +-
.../sis/storage/sql/feature/TableAnalyzer.java | 10 +
.../sis/storage/sql/feature/ValueGetter.java | 2 +-
.../apache/sis/storage/sql/postgis/Postgres.java | 4 +-
.../org/apache/sis/util/stream/DeferredStream.java | 10 +-
.../org/apache/sis/storage/sql/SQLStoreTest.java | 30 +-
.../org/apache/sis/io/stream/ChannelDataInput.java | 15 +
.../org/apache/sis/io/stream/HttpByteChannel.java | 21 +
.../main/org/apache/sis/storage/FeatureQuery.java | 58 +-
.../main/org/apache/sis/storage/FeatureSubset.java | 6 +-
.../apache/sis/storage/base/FeatureProjection.java | 381 -----------
.../sis/storage/base/TiledDeferredImage.java | 1 +
.../apache/sis/storage/base/TiledGridCoverage.java | 19 +-
.../org/apache/sis/storage/internal/Resources.java | 5 +
.../sis/storage/internal/Resources.properties | 1 +
.../sis/storage/internal/Resources_fr.properties | 1 +
.../main/org/apache/sis/storage/tiling/Tile.java | 19 +
.../sis/util/privy/UnmodifiableArrayList.java | 3 +-
.../apache/sis/storage/geoheif/FromImageIO.java | 65 +-
.../main/org/apache/sis/storage/geoheif/Image.java | 43 +-
.../apache/sis/storage/geoheif/ImageResource.java | 152 +++--
.../sis/storage/geoheif/ResourceBuilder.java | 6 +-
.../sis/storage/geoheif/UncompressedImage.java | 69 +-
.../org/apache/sis/storage/isobmff/ByteRanges.java | 163 +++++
.../org/apache/sis/storage/isobmff/ByteReader.java | 92 ---
.../org/apache/sis/storage/isobmff/Reader.java | 14 +-
.../apache/sis/storage/isobmff/base/ItemData.java | 32 +-
.../sis/storage/isobmff/base/ItemLocation.java | 96 +--
.../sis/storage/shapefile/ShapefileStore.java | 12 +-
84 files changed, 3048 insertions(+), 1107 deletions(-)
create mode 100644 endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureProjection.java
create mode 100644 endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureProjectionBuilder.java
create mode 100644 endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureView.java
copy endorsed/src/org.apache.sis.feature/main/org/apache/sis/{coverage/MismatchedCoverageRangeException.java => filter/InvalidXPathException.java} (54%)
create mode 100644 endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/ConverterTo2D.java
delete mode 100644 endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/FeatureProjection.java
create mode 100644 incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/ByteRanges.java
delete mode 100644 incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/ByteReader.java
(sis) 01/01: Merge branch 'geoapi-3.1'. Contains improvements in: `SQLStore`, ShapeFile (incubator), conversions from AWT shapes to JTS, partial DuckDB support, and use of HTTP ranges in GeoHEIF.
Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/sis.git
commit 3d377745a9cbb746e07f482fe71082dcfa657854
Merge: ac4ad3f36a ffa96dbde4
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Wed May 21 18:14:30 2025 +0200
Merge branch 'geoapi-3.1'.
Contains improvements in: `SQLStore`, ShapeFile (incubator), conversions from AWT shapes to JTS, partial DuckDB support, and use of HTTP ranges in GeoHEIF.
.../apache/sis/cloud/aws/s3/ClientFileSystem.java | 2 +-
.../org.apache.sis.feature/main/module-info.java | 1 +
.../org/apache/sis/feature/AbstractOperation.java | 4 +-
.../apache/sis/feature/ExpressionOperation.java | 2 +-
.../org/apache/sis/feature/FeatureOperations.java | 77 ++-
.../main/org/apache/sis/feature/Features.java | 25 +-
.../apache/sis/feature/StringJoinOperation.java | 14 +
.../sis/feature/builder/AttributeTypeBuilder.java | 14 +-
.../sis/feature/builder/FeatureTypeBuilder.java | 10 +-
.../sis/feature/builder/OperationWrapper.java | 30 +-
.../sis/feature/builder/PropertyTypeBuilder.java | 32 +-
.../apache/sis/feature/builder/TypeBuilder.java | 2 +-
.../org/apache/sis/feature/internal/Resources.java | 5 +
.../sis/feature/internal/Resources.properties | 1 +
.../sis/feature/internal/Resources_fr.properties | 1 +
.../main/org/apache/sis/feature/package-info.java | 2 +-
.../sis/feature/privy/FeatureExpression.java | 83 ++-
.../sis/feature/privy/FeatureProjection.java | 341 ++++++++++
.../feature/privy/FeatureProjectionBuilder.java | 739 +++++++++++++++++++++
.../org/apache/sis/feature/privy/FeatureView.java | 129 ++++
.../org/apache/sis/filter/ArithmeticFunction.java | 8 +-
.../org/apache/sis/filter/AssociationValue.java | 22 +-
.../org/apache/sis/filter/ConvertFunction.java | 21 +-
.../apache/sis/filter/DefaultFilterFactory.java | 26 +-
.../apache/sis/filter/InvalidXPathException.java | 78 +++
.../main/org/apache/sis/filter/LeafExpression.java | 15 +-
.../main/org/apache/sis/filter/PropertyValue.java | 72 +-
.../main/org/apache/sis/filter/privy/XPath.java | 18 +-
.../apache/sis/filter/sqlmm/FunctionWithSRID.java | 24 +-
.../apache/sis/filter/sqlmm/SpatialFunction.java | 36 +-
.../sis/geometry/wrapper/jts/ConverterTo2D.java | 227 +++++++
.../sis/geometry/wrapper/jts/ShapeConverter.java | 73 +-
.../feature/builder/FeatureTypeBuilderTest.java | 29 +
.../geometry/wrapper/jts/ShapeConverterTest.java | 40 +-
.../org/apache/sis/metadata/sql/privy/Dialect.java | 65 +-
.../apache/sis/metadata/sql/privy/Reflection.java | 2 +
.../apache/sis/metadata/sql/privy/Supports.java | 15 +
.../main/org/apache/sis/storage/landsat/Band.java | 27 +-
.../org/apache/sis/storage/landsat/BandGroup.java | 15 +
.../apache/sis/storage/landsat/LandsatStore.java | 27 +-
.../org/apache/sis/storage/sql/duckdb/DuckDB.java | 2 +-
.../sis/storage/sql/duckdb/package-info.java | 4 +
.../apache/sis/storage/sql/feature/Analyzer.java | 7 +-
.../org/apache/sis/storage/sql/feature/Column.java | 50 +-
.../apache/sis/storage/sql/feature/Database.java | 56 +-
.../sis/storage/sql/feature/FeatureAdapter.java | 20 +-
.../sis/storage/sql/feature/FeatureIterator.java | 31 +-
.../sis/storage/sql/feature/FeatureStream.java | 38 +-
.../sis/storage/sql/feature/GeometryEncoding.java | 93 ++-
.../storage/sql/feature/GeometryTypeEncoding.java | 2 +
.../sis/storage/sql/feature/InfoStatements.java | 124 +++-
.../sis/storage/sql/feature/QueryAnalyzer.java | 7 +-
.../sis/storage/sql/feature/SelectionClause.java | 10 +-
.../storage/sql/feature/SelectionClauseWriter.java | 10 +-
.../sis/storage/sql/feature/SpatialSchema.java | 9 +-
.../org/apache/sis/storage/sql/feature/Table.java | 48 +-
.../sis/storage/sql/feature/TableAnalyzer.java | 10 +
.../sis/storage/sql/feature/ValueGetter.java | 2 +-
.../apache/sis/storage/sql/postgis/Postgres.java | 4 +-
.../org/apache/sis/util/stream/DeferredStream.java | 10 +-
.../org/apache/sis/storage/sql/SQLStoreTest.java | 30 +-
.../org/apache/sis/io/stream/ChannelDataInput.java | 15 +
.../org/apache/sis/io/stream/HttpByteChannel.java | 21 +
.../main/org/apache/sis/storage/FeatureQuery.java | 58 +-
.../main/org/apache/sis/storage/FeatureSubset.java | 6 +-
.../apache/sis/storage/base/FeatureProjection.java | 381 -----------
.../sis/storage/base/TiledDeferredImage.java | 1 +
.../apache/sis/storage/base/TiledGridCoverage.java | 19 +-
.../org/apache/sis/storage/internal/Resources.java | 5 +
.../sis/storage/internal/Resources.properties | 1 +
.../sis/storage/internal/Resources_fr.properties | 1 +
.../main/org/apache/sis/storage/tiling/Tile.java | 19 +
.../sis/util/privy/UnmodifiableArrayList.java | 3 +-
.../apache/sis/storage/geoheif/FromImageIO.java | 65 +-
.../main/org/apache/sis/storage/geoheif/Image.java | 43 +-
.../apache/sis/storage/geoheif/ImageResource.java | 152 +++--
.../sis/storage/geoheif/ResourceBuilder.java | 6 +-
.../sis/storage/geoheif/UncompressedImage.java | 69 +-
.../org/apache/sis/storage/isobmff/ByteRanges.java | 163 +++++
.../org/apache/sis/storage/isobmff/ByteReader.java | 92 ---
.../org/apache/sis/storage/isobmff/Reader.java | 14 +-
.../apache/sis/storage/isobmff/base/ItemData.java | 32 +-
.../sis/storage/isobmff/base/ItemLocation.java | 96 +--
.../sis/storage/shapefile/ShapefileStore.java | 12 +-
84 files changed, 3099 insertions(+), 1096 deletions(-)
diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/ExpressionOperation.java
index 1ed310c507,5d27304400..0449cc2a29
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/ExpressionOperation.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/ExpressionOperation.java
@@@ -57,7 -62,7 +57,7 @@@ final class ExpressionOperation<V> exte
* The expression to which to delegate the execution of this operation.
*/
@SuppressWarnings("serial") // Not statically typed as serializable.
- private final Function<? super AbstractFeature, ? extends V> expression;
- final Function<? super Feature, ? extends V> expression;
++ final Function<? super AbstractFeature, ? extends V> expression;
/**
* The type of result of evaluating the expression.
diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureOperations.java
index 2dc4406318,05210b25e4..4df8b93098
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureOperations.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureOperations.java
@@@ -27,10 -27,17 +27,12 @@@ import org.apache.sis.util.Static
import org.apache.sis.util.collection.WeakHashSet;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.privy.Strings;
+ import org.apache.sis.filter.DefaultFilterFactory;
+ import org.apache.sis.filter.privy.XPath;
import org.apache.sis.setup.GeometryLibrary;
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-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;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
/**
@@@ -158,9 -152,9 +151,9 @@@ public final class FeatureOperations ex
* @param referent the referenced attribute or feature association.
* @return an operation which is an alias for the {@code referent} property.
*
-- * @see Features#getLinkTarget(PropertyType)
++ * @see Features#getLinkTarget(AbstractIdentifiedType)
*/
- public static Operation link(final Map<String,?> identification, final PropertyType referent) {
+ public static AbstractOperation link(final Map<String,?> identification, final AbstractIdentifiedType referent) {
ArgumentChecks.ensureNonNull("referent", referent);
return POOL.unique(new LinkOperation(identification, referent));
}
@@@ -362,4 -348,44 +355,44 @@@
{
return function(identification, expression.toValueType(resultType.getValueClass()), resultType);
}
+
+ /**
+ * Returns an expression for fetching the values of properties identified by the given type.
+ * The returned expression will be the first of the following choices which is applicable:
+ *
+ * <ul>
+ * <li>If the property is an expression built by {@link #expression expression(…)}, then the
+ * expression given to that method, or a derivative of that expression, is returned.</li>
+ * <li>If the property {@linkplain Features#getLinkTarget is a link},
+ * then a {@code ValueReference} fetching the link target is returned.</li>
+ * <li>Otherwise, a {@linkplain DefaultFilterFactory.Features#property value reference}
+ * is created for the name of the given property.</li>
+ * </ul>
+ *
+ * @param property the property for which to get an expression.
+ * @return an expression for fetching the values of the property identified by the given type.
+ * @since 1.5
+ */
- public static Expression<? super Feature, ?> expressionOf(final PropertyType property) {
++ public static Expression<? super AbstractFeature, ?> expressionOf(final AbstractIdentifiedType property) {
+ // Test final class first because it is fast.
+ if (property instanceof ExpressionOperation<?>) {
- final Function<? super Feature, ?> expression = ((ExpressionOperation<?>) property).expression;
++ final Function<? super AbstractFeature, ?> expression = ((ExpressionOperation<?>) property).expression;
+ if (expression instanceof Expression<?,?>) {
- return (Expression<? super Feature, ?>) expression;
++ return (Expression<? super AbstractFeature, ?>) expression;
+ }
+ }
+ String name;
+ final Class<?> type;
- if (property instanceof AttributeType<?>) {
- type = ((AttributeType<?>) property).getValueClass();
++ if (property instanceof DefaultAttributeType<?>) {
++ type = ((DefaultAttributeType<?>) property).getValueClass();
+ name = null;
+ } else {
+ type = Object.class;
+ name = Features.getLinkTarget(property).orElse(null);
+ }
+ if (name == null) {
+ name = property.getName().toString();
+ }
+ return DefaultFilterFactory.forFeatures().property(XPath.fromPropertyName(name), type);
+ }
}
diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/Features.java
index ea862f408f,00ad38f9d1..90d7b2d5e4
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/Features.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/Features.java
@@@ -260,6 -302,28 +261,28 @@@ public final class Features extends Sta
return Optional.empty();
}
+ /**
+ * If the given property is a link or a compound key, returns the name of the referenced properties.
- * This method is similar to {@link #getLinkTarget(PropertyType)}, except that it recognizes also
++ * This method is similar to {@link #getLinkTarget(AbstractIdentifiedType)}, except that it recognizes also
+ * the operations created by {@link FeatureOperations#compound FeatureOperations.compound(…)}.
+ *
+ * @param property the property to test, or {@code null} if none.
+ * @return the referenced property names if {@code property} is a link or a compound key,
+ * or an empty list otherwise.
+ *
- * @see FeatureOperations#compound(Map, String, String, String, PropertyType...)
++ * @see FeatureOperations#compound(Map, String, String, String, AbstractIdentifiedType...)
+ *
+ * @since 1.5
+ */
- public static List<String> getLinkTargets(final PropertyType property) {
++ public static List<String> getLinkTargets(final AbstractIdentifiedType property) {
+ return getLinkTarget(property).map(List::of).orElseGet(() -> {
+ if (property instanceof StringJoinOperation) {
+ return ((StringJoinOperation) property).getAttributeNames();
+ }
+ return List.of();
+ });
+ }
+
/**
* Ensures that all characteristics and property values in the given feature are valid.
* An attribute is valid if it contains a number of values between the
diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/StringJoinOperation.java
index 75bcc06d7f,703c02faac..bb3d2431a0
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/StringJoinOperation.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/StringJoinOperation.java
@@@ -35,8 -36,21 +36,9 @@@ import org.apache.sis.util.privy.Collec
import org.apache.sis.converter.SurjectiveConverter;
import org.apache.sis.feature.privy.AttributeConvention;
import org.apache.sis.feature.internal.Resources;
+ import org.apache.sis.util.privy.UnmodifiableArrayList;
import org.apache.sis.util.resources.Errors;
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.InvalidPropertyValueException;
-import org.opengis.feature.Operation;
-import org.opengis.feature.Property;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.PropertyNotFoundException;
-
/**
* An operation concatenating the string representations of the values of multiple properties.
diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/PropertyTypeBuilder.java
index 7ce9c5ea4e,2c9405313e..d062c56da1
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/PropertyTypeBuilder.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/PropertyTypeBuilder.java
@@@ -18,9 -18,13 +18,10 @@@ package org.apache.sis.feature.builder
import org.opengis.util.GenericName;
import org.apache.sis.util.resources.Errors;
+ import org.apache.sis.feature.internal.Resources;
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.FeatureAssociationRole;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractIdentifiedType;
/**
diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureExpression.java
index 9c1b95725a,03dbf4913a..cdf5e57109
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureExpression.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureExpression.java
@@@ -18,18 -18,20 +18,15 @@@ package org.apache.sis.feature.privy
import java.util.Set;
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.feature.builder.FeatureTypeBuilder;
- import org.apache.sis.feature.builder.PropertyTypeBuilder;
- import org.apache.sis.feature.builder.AttributeTypeBuilder;
import org.apache.sis.filter.internal.Node;
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.PropertyNotFoundException;
-import org.opengis.filter.InvalidFilterValueException;
-import org.opengis.filter.Literal;
-import org.opengis.filter.Expression;
-import org.opengis.filter.ValueReference;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
- import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.pending.geoapi.filter.Literal;
+import org.apache.sis.pending.geoapi.filter.ValueReference;
/**
@@@ -72,21 -74,38 +69,38 @@@ public interface FeatureExpression<R,V
}
/**
- * Provides the expected type of values produced by this expression when a feature of the given
- * type is evaluated. The resulting type shall describe a "static" property, i.e. it can be a
- * {@link org.apache.sis.feature.DefaultAttributeType} or a {@link org.apache.sis.feature.DefaultAssociationRole}
- * but not an {@link org.apache.sis.feature.AbstractOperation}.
+ * Provides the expected type of values produced by this expression when a feature of a given type is evaluated.
+ * Except for the special case of links (described below), the resulting type shall describe a "static" property,
- * <i>i.e.</i> the type should be an {@link AttributeType} or a {@link org.opengis.feature.FeatureAssociationRole}
- * but not an {@link org.opengis.feature.Operation}. The value of the static property will be set to the result of
++ * <i>i.e.</i> the type should be an {@code AttributeType} or a {@code FeatureAssociationRole}
++ * but not an {@code Operation}. The value of the static property will be set to the result of
+ * evaluating the expression when instances of the {@code FeatureType} will be created.
- * This evaluation will be performed by {@link FeatureProjection#apply(Feature)}.
++ * This evaluation will be performed by {@link FeatureProjection#apply(AbstractFeature)}.
*
- * <p>If this method returns an instance of {@link AttributeTypeBuilder}, then its parameterized
- * type should be the same {@code <V>} than this {@code FeatureExpression}.</p>
+ * <h4>Implementation guideline</h4>
+ * Implementations should declare the property by invoking some of the following methods:
*
- * @param valueType the type of features to be evaluated by the given expression.
- * @param addTo where to add the type of properties evaluated by this expression.
- * @return builder of the added property, or {@code null} if this method cannot add a property.
- * @throws IllegalArgumentException if this method can operate only on some feature types
- * and the given type is not one of them.
+ * <ul>
+ * <li>{@link FeatureProjectionBuilder#source()} for the source of the {@link PropertyType} in next point.</li>
- * <li>{@link FeatureProjectionBuilder#addSourceProperty(PropertyType, boolean)}</li>
++ * <li>{@link FeatureProjectionBuilder#addSourceProperty(AbstractIdentifiedType, boolean)}</li>
+ * <li>{@link FeatureProjectionBuilder#addComputedProperty(PropertyTypeBuilder, boolean)}</li>
+ * </ul>
+ *
+ * Inherited methods such as {@link FeatureProjectionBuilder#addAttribute(Class)} can also be invoked,
+ * but callers will be responsible for providing the value of the properties added by those methods.
- * These values will not be provided by {@link FeatureProjection#apply(Feature)}.
++ * These values will not be provided by {@link FeatureProjection#apply(AbstractFeature)}.
+ *
+ * <h4>Operations</h4>
+ * If the property is a link to another property, such as {@code "sis:identifier"} or {@code "sis:geometry"},
+ * then adding this property may require the addition of dependencies. These dependencies will be detected by
+ * {@link FeatureProjectionBuilder}, which may generate an intermediate {@code FeatureType}.
+ *
+ * @param addTo where to add the type of the property evaluated by this expression.
+ * @return handler of the added property, or {@code null} if the property cannot be added.
- * @throws InvalidFilterValueException if this expression is invalid for the requested operation.
- * @throws PropertyNotFoundException if the property was not found in {@code addTo.source()}.
++ * @throws IllegalArgumentException if this expression is invalid for the requested operation.
++ * @throws IllegalArgumentException if the property was not found in {@code addTo.source()}.
+ * @throws UnconvertibleObjectException if the property default value cannot be converted to the expected type.
*/
- PropertyTypeBuilder expectedType(DefaultFeatureType valueType, FeatureTypeBuilder addTo);
+ FeatureProjectionBuilder.Item expectedType(FeatureProjectionBuilder addTo);
/**
* Tries to cast or convert the given expression to a {@link FeatureExpression}.
diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureProjection.java
index 0000000000,b263c1e4dc..9796665544
mode 000000,100644..100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureProjection.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureProjection.java
@@@ -1,0 -1,341 +1,341 @@@
+ /*
+ * 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.privy;
+
+ import java.util.List;
+ import java.util.Set;
+ import java.util.Map;
+ import java.util.LinkedHashMap;
+ import java.util.Optional;
+ import java.util.function.UnaryOperator;
+ import org.apache.sis.util.Debug;
+ import org.apache.sis.util.ArraysExt;
+ import org.apache.sis.util.resources.Vocabulary;
+ import org.apache.sis.util.privy.UnmodifiableArrayList;
+ import org.apache.sis.filter.privy.ListingPropertyVisitor;
+ import org.apache.sis.io.TableAppender;
+
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.Expression;
-import org.opengis.filter.ValueReference;
++// Specific to the main branch:
++import org.apache.sis.filter.Expression;
++import org.apache.sis.feature.AbstractFeature;
++import org.apache.sis.feature.DefaultFeatureType;
++import org.apache.sis.pending.geoapi.filter.ValueReference;
+
+
+ /**
+ * A function applying projections (with "projection" in the <abbr>SQL</abbr> database sense) of features.
+ * Given full feature instances in input, the function returns feature instances containing only a subset
+ * of the properties. The property values may also be different if they are computed by the expressions.
+ *
+ * @author Guilhem Legal (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
+ */
-public final class FeatureProjection implements UnaryOperator<Feature> {
++public final class FeatureProjection implements UnaryOperator<AbstractFeature> {
+ /**
+ * The type of features with the properties explicitly requested by the user.
+ * The property names may differ from the properties of the {@link FeatureProjectionBuilder#source() source}
+ * features if aliases were specified by calls to {@link FeatureProjectionBuilder.Item#setName(GenericName)}.
+ */
- public final FeatureType typeRequested;
++ public final DefaultFeatureType typeRequested;
+
+ /**
+ * The requested type augmented with dependencies required for the execution of operations such as links.
+ * If there is no need for additional properties, then this value is the same as {@link #typeRequested}.
+ * The property names are the same as {@link #typeRequested} (i.e., may be aliases).
+ */
- public final FeatureType typeWithDependencies;
++ public final DefaultFeatureType typeWithDependencies;
+
+ /**
+ * Names of the properties to be stored in the feature instances created by this {@code FeatureProjection}.
+ * Properties that are computed on-the-fly from other properties are not included in this array. Properties
+ * that are present in {@link #typeRequested} but not in the source features are not in this array neither.
+ */
+ private final String[] propertiesToCopy;
+
+ /**
+ * Expressions to apply on the source feature for fetching the property values of the projected feature.
+ * This array has the same length as {@link #propertiesToCopy} and each expression is associated to the
+ * property at the same index.
+ */
- private final Expression<? super Feature, ?>[] expressions;
++ private final Expression<? super AbstractFeature, ?>[] expressions;
+
+ /**
- * Whether the {@link #apply(Feature)} method shall create instances of {@link #typeWithDependencies}.
- * If {@code false}, then the instances given to the {@link #apply(Feature)} method will be assumed
++ * Whether the {@link #apply(AbstractFeature)} method shall create instances of {@link #typeWithDependencies}.
++ * If {@code false}, then the instances given to the {@link #apply(AbstractFeature)} method will be assumed
+ * to be already instances of {@link #typeWithDependencies} and will be modified in-place.
+ */
+ private final boolean createInstance;
+
+ /**
+ * Creates a new projection with the given properties specified by a builder.
- * The {@link #apply(Feature)} method will copy the properties of the given
++ * The {@link #apply(AbstractFeature)} method will copy the properties of the given
+ * features into new instances of {@link #typeWithDependencies}.
+ *
+ * @param typeRequested the type of projected features.
+ * @param projection descriptions of the properties to keep in the projected features.
+ */
- FeatureProjection(final FeatureType typeRequested, final FeatureType typeWithDependencies,
++ FeatureProjection(final DefaultFeatureType typeRequested, final DefaultFeatureType typeWithDependencies,
+ final List<FeatureProjectionBuilder.Item> projection)
+ {
+ this.createInstance = true;
+ this.typeRequested = typeRequested;
+ this.typeWithDependencies = typeWithDependencies;
+ int storedCount = 0;
+
+ // Expressions to apply on the source feature for fetching the property values of the projected feature.
+ @SuppressWarnings({"LocalVariableHidesMemberVariable", "unchecked", "rawtypes"})
- final Expression<? super Feature,?>[] expressions = new Expression[projection.size()];
++ final Expression<? super AbstractFeature,?>[] expressions = new Expression[projection.size()];
+
+ // Names of the properties to be stored in the attributes of the target features.
+ @SuppressWarnings("LocalVariableHidesMemberVariable")
+ final String[] propertiesToCopy = new String[expressions.length];
+
+ for (final FeatureProjectionBuilder.Item item : projection) {
+ final var expression = item.attributeValueGetter();
+ if (expression != null) {
+ expressions[storedCount] = expression;
+ propertiesToCopy[storedCount++] = item.getName();
+ }
+ }
+ this.propertiesToCopy = ArraysExt.resize(propertiesToCopy, storedCount);
+ this.expressions = ArraysExt.resize(expressions, storedCount);
+ }
+
+ /**
+ * Creates a new projection with a subset of the properties of another projection.
+ * This constructor is invoked when the caller handles itself some of the properties.
+ *
+ * <h4>behavioral change</h4>
+ * Projections created by this constructor assumes that the feature instances given to the
- * {@link #apply(Feature)} method are already instances of {@link #typeWithDependencies}
++ * {@link #apply(AbstractFeature)} method are already instances of {@link #typeWithDependencies}
+ * and can be modified (if needed) in place. This constructor is designed for cases where
+ * the caller does itself a part of the {@code FeatureProjection} work.
+ *
+ * @param parent the projection from which to inherit the types and expressions.
+ * @param remaining index of the properties that still need to be copied after the caller did its processing.
+ *
+ * @see #afterPreprocessing(int[])
+ */
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ private FeatureProjection(final FeatureProjection parent, final int[] remaining) {
+ createInstance = false;
+ typeRequested = parent.typeRequested;
+ typeWithDependencies = parent.typeWithDependencies;
+ expressions = new Expression[remaining.length];
+ propertiesToCopy = new String[remaining.length];
+ for (int i=0; i<remaining.length; i++) {
+ final int index = remaining[i];
+ propertiesToCopy[i] = parent.propertiesToCopy[index];
+ expressions[i] = parent.expressions[index];
+ }
+ }
+
+ /**
+ * Returns a variant of this projection where the caller has created the target feature instance itself.
+ * The callers is may have set some property values itself, and the {@code remaining} argument gives the
+ * indexes of the properties that still need to be copied after caller's processing.
+ *
+ * @param remaining index of the properties that still need to be copied after the caller did its processing.
+ * @return a variant of this projection which only completes the projection done by the caller,
+ * or {@code null} if there is nothing left to complete.
+ */
+ public FeatureProjection afterPreprocessing(final int[] remaining) {
+ if (remaining.length == 0 && typeRequested == typeWithDependencies) {
+ return null;
+ }
+ return new FeatureProjection(this, remaining);
+ }
+
+ /**
+ * Returns the names of all stored properties. This list may be shorter than the list of properties of the
+ * {@linkplain #typeRequested requested feature type} if some feature properties are computed on-the-fly,
+ * or if the target feature contains some new properties that are not in the source.
+ *
+ * @return the name of all stored properties.
+ */
+ public final List<String> propertiesToCopy() {
+ return UnmodifiableArrayList.wrap(propertiesToCopy);
+ }
+
+ /**
+ * Returns the path to the value (in source features) of the property at the given index.
+ * The argument corresponds to an index in the list returned by {@link #propertiesToCopy()}.
+ * The return value is often the same name as {@code propertiesToCopy().get(index)} but may
+ * differ if the user has specified aliases or if two properties have the same default name.
+ *
+ * @param index index of the stored property for which to get the name in the source feature.
+ * @return path in the source features, or empty if the property is not a {@link ValueReference}.
+ */
+ public Optional<String> xpath(final int index) {
- final Expression<? super Feature, ?> expression = expressions[index];
++ final Expression<? super AbstractFeature, ?> expression = expressions[index];
+ if (expression instanceof ValueReference<?,?>) {
+ return Optional.of(((ValueReference<?,?>) expression).getXPath());
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * Returns all dependencies used, directly or indirectly, by all expressions used in this projection.
+ * The set includes transitive dependencies (expressions with operands that are other expressions).
+ * The elements are XPaths.
+ *
+ * @return all dependencies (including transitive dependencies) as XPaths.
+ */
+ public Set<String> dependencies() {
+ Set<String> references = null;
+ for (var expression : expressions) {
+ references = ListingPropertyVisitor.xpaths(expression, references);
+ }
+ return (references != null) ? references : Set.of();
+ }
+
+ /**
+ * Derives a new projected feature instance from the given source.
+ * The feature type of the returned feature instance will be be {@link #typeRequested}.
+ * This method performs the following steps:
+ *
+ * <ol class="verbose">
+ * <li>If this projection was created by {@link #afterPreprocessing(int[])}, then the given feature
+ * shall be an instances of {@link #typeWithDependencies} and may be modified in-place. Otherwise,
+ * this method creates a new instance of {@link #typeWithDependencies}.</li>
+ * <li>This method executes all expressions for fetching values from {@code source}
+ * and stores the results in the feature instance of above step.</li>
+ * <li>If {@link #typeWithDependencies} is different than {@link #typeRequested}, then the feature
+ * of above step is wrapped in a view which hides the undesired properties.</li>
+ * </ol>
+ *
+ * @param source the source feature instance.
+ * @return the "projected" (<abbr>SQL</abbr> database sense) feature instance.
+ */
+ @Override
- public Feature apply(final Feature source) {
++ public AbstractFeature apply(final AbstractFeature source) {
+ var feature = createInstance ? typeWithDependencies.newInstance() : source;
+ for (int i=0; i < expressions.length; i++) {
+ feature.setPropertyValue(propertiesToCopy[i], expressions[i].apply(source));
+ }
+ if (typeRequested != typeWithDependencies) {
+ feature = new FeatureView(typeRequested, feature);
+ }
+ return feature;
+ }
+
+ /**
+ * Returns a string representation of this projection for debugging purposes.
+ * The current implementation formats a table with all properties, including
+ * dependencies, and a column saying whether the property is an operation,
+ * is stored or whether there is an error.
+ *
+ * @return a string representation.
+ */
+ @Override
+ public String toString() {
+ return Row.toString(this, propertiesToCopy);
+ }
+
+ /**
+ * Helper class for the implementation of {@link FeatureProjection#toString()}.
+ * Each instance represents a row in the table to be formatted..
+ * This is used for debugging purposes only.
+ */
+ @Debug
+ private static final class Row {
+ /**
+ * Returns a string representation of the {@link FeatureProjection} having the given values.
+ * Having this method in a separated class reduces the amount of classes loading, since this
+ * {@code Row} class is rarely needed in production environment.
+ *
+ * @param projection the projection for which to format a string representation.
+ * @param propertiesToCopy value of {@link FeatureProjection#propertiesToCopy}.
+ * @return the string representation of the given projection.
+ */
+ static String toString(final FeatureProjection projection, final String[] propertiesToCopy) {
+ final var rowByName = new LinkedHashMap<String,Row>();
+ if (projection.typeWithDependencies != projection.typeRequested) {
+ addAll(rowByName, projection.typeWithDependencies, "dependency");
+ }
+ addAll(rowByName, projection.typeRequested, "operation"); // Overwrite above dependencies.
+ for (int i=0; i < propertiesToCopy.length; i++) {
+ String name = propertiesToCopy[i];
+ String value;
+ try {
+ name = projection.typeWithDependencies.getProperty(name).getName().toString();
+ value = "stored";
+ } catch (RuntimeException e) {
+ value = e.toString();
+ }
+ Row row = rowByName.computeIfAbsent(name, Row::new);
+ row.type = value;
+ row.xpath = projection.xpath(i).orElse("");
+ }
+ final var words = Vocabulary.forLocale(null);
+ final var table = new TableAppender(" │ ");
+ table.setMultiLinesCells(true);
+ table.appendHorizontalSeparator();
+ table.append(words.getString(Vocabulary.Keys.Property)).nextColumn();
+ table.append(words.getString(Vocabulary.Keys.Type)).nextColumn();
+ table.append("XPath").nextLine();
+ table.appendHorizontalSeparator();
+ for (final Row row : rowByName.values()) {
+ table.append(row.property).nextColumn();
+ table.append(row.type).nextColumn();
+ table.append(row.xpath).nextLine();
+ }
+ table.appendHorizontalSeparator();
+ return table.toString();
+ }
+
+ /**
+ * Adds all properties of the given feature type into the specified map.
+ * For each property, {@link #type} is overwritten with the {@code type} argument value.
+ *
+ * @param rowByName where to add the properties.
+ * @param featureType the type from which to get the list of properties.
+ * @param type the "stored", "operation" or "dependency" value to assign to {@link #type}.
+ */
- private static void addAll(final Map<String,Row> rowByName, final FeatureType featureType, final String type) {
++ private static void addAll(final Map<String,Row> rowByName, final DefaultFeatureType featureType, final String type) {
+ for (final var property : featureType.getProperties(true)) {
+ Row row = rowByName.computeIfAbsent(property.getName().toString(), Row::new);
+ row.type = type;
+ }
+ }
+
+ /**
+ * Creates a row with no value.
+ *
+ * @param name number under which the property is stored.
+ */
+ private Row(final String name) {
+ property = name;
+ xpath = "";
+ }
+
+ /**
+ * Name of the property in the projected feature type.
+ */
+ private final String property;
+
+ /**
+ * Path to the property value in the source feature.
+ */
+ private String xpath;
+
+ /**
+ * Type: stored, operation or dependency.
+ */
+ private String type;
+ }
+ }
diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureProjectionBuilder.java
index 0000000000,2f9c7bd6df..d3d6af82db
mode 000000,100644..100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureProjectionBuilder.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureProjectionBuilder.java
@@@ -1,0 -1,741 +1,739 @@@
+ /*
+ * 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.privy;
+
+ import java.util.Map;
+ import java.util.HashMap;
+ import java.util.List;
+ import java.util.ArrayList;
+ import java.util.Iterator;
+ import java.util.Locale;
+ import java.util.Objects;
+ import java.util.Optional;
+ import java.util.function.UnaryOperator;
+ import org.opengis.util.GenericName;
+ import org.opengis.referencing.crs.CoordinateReferenceSystem;
+ import org.apache.sis.feature.Features;
+ import org.apache.sis.feature.AbstractOperation;
+ import org.apache.sis.feature.FeatureOperations;
+ import org.apache.sis.feature.builder.AssociationRoleBuilder;
+ import org.apache.sis.feature.builder.AttributeTypeBuilder;
+ import org.apache.sis.feature.builder.FeatureTypeBuilder;
+ import org.apache.sis.feature.builder.PropertyTypeBuilder;
+ import org.apache.sis.util.ArgumentCheckByAssertion;
+ import org.apache.sis.util.UnconvertibleObjectException;
+ import org.apache.sis.util.privy.Strings;
+ import org.apache.sis.util.resources.Errors;
+ import org.apache.sis.util.resources.Vocabulary;
+
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.Operation;
-import org.opengis.filter.Expression;
-import org.opengis.filter.ValueReference;
++// Specific to the main branch:
++import org.apache.sis.filter.Expression;
++import org.apache.sis.feature.AbstractFeature;
++import org.apache.sis.feature.DefaultFeatureType;
++import org.apache.sis.feature.DefaultAttributeType;
++import org.apache.sis.feature.AbstractIdentifiedType;
++import org.apache.sis.pending.geoapi.filter.ValueReference;
+
+
+ /**
+ * A builder for deriving a feature type containing a subset of the properties of another type.
+ * The other type is called the {@linkplain #source() source} feature type. The properties that
+ * are retained may have different names than the property names of the source feature type.
+ * If a property is a link such as {@code sis:identifier} or {@code sis:geometry},
+ * this class keeps trace of the dependencies required for recreating the link.
+ *
+ * <p>Properties that are copied from the source feature type are declared by calls to
- * {@link #addSourceProperty(PropertyType, boolean)} and related methods defined in this class.
++ * {@link #addSourceProperty(AbstractIdentifiedType, boolean)} and related methods defined in this class.
+ * The methods inherited from the parent class can also be invoked,
+ * but they will receive no special treatment.</p>
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ */
+ public final class FeatureProjectionBuilder extends FeatureTypeBuilder {
+ /**
+ * The type of features that provide the values to store in the projected features.
+ * The value of this field does not change, except when following a XPath such as {@code "a/b/c"}.
+ *
+ * @see #source()
+ */
- private FeatureType source;
++ private DefaultFeatureType source;
+
+ /**
+ * Whether the source is a dependency of the feature type given to the constructor.
+ * This flag become {@code true} when following a XPath of the form {@code "a/b/c"}.
+ * In such case, {@link #source} may be temporarily set to the tip {@code "c"} type.
+ *
- * @see #using(FeatureType, FeatureExpression)
++ * @see #using(DefaultFeatureType, FeatureExpression)
+ */
+ private boolean sourceIsDependency;
+
+ /**
+ * The properties to inherit from the {@linkplain #source} feature type by explicit user's request.
+ * The property types and the property names are not necessarily the same as in the source feature.
+ * For example, an operation may be replaced by an attribute which will store the operation result.
+ *
+ * <h4>Implementation note</h4>
+ * This collection cannot be a {@link java.util.Map} with names of the source properties as keys,
+ * because the list may contain more than one item with the same {@link Item#sourceName} value.
+ * This collision happens if some items are the results of XPath evaluations such as {@code "a/b/c"}.
+ * This is the reason why properties can be renamed before to be stored in the projected features.
+ */
+ private final List<Item> requested;
+
+ /**
+ * Names that are actually used, or may be used, in the projected feature type.
+ * For each name, the associated value is the item that is explicitly using that name.
+ * A {@code null} value means that the name is not used, but is nevertheless reserved
+ * because potentially ambiguous. This information is used for avoiding name collisions
+ * in automatically generated names.
+ *
+ * <p>Note that the keys are not necessarily the values of {@link Item#sourceName}.
+ * Keys are rather the values of {@code Item.builder.getName()}, except that the
+ * latter may not be valid before {@link Item#validateName()} is invoked.</p>
+ *
+ * @see #reserve(GenericName, Item)
+ */
+ private final Map<GenericName, Item> reservedNames;
+
+ /**
+ * Whether at least one item is modified compared to the original property in the source feature type.
+ * A modified item may be an item with a name different than the property in {@linkplain #source}.
+ * If {@code true}, then the projection cannot be an {@linkplain #isIdentity() identity} operation.
+ * Result of operations such as links may also need to be fetched in advance,
+ * because operations cannot be executed anymore after the name of a dependency changed.
+ *
+ * @see Item#setName(GenericName)
+ * @see #isIdentity()
+ */
+ private boolean hasModifiedProperties;
+
+ /**
+ * Sequential number for generating default names of unnamed properties. This is used when the
+ * name inherited from the {@linkplain #source} feature is unknown or collides with another name.
+ */
+ private int unnamedNumber;
+
+ /**
+ * Names of {@linkplain #source} properties that are dependencies found in operations.
+ * The most common cases are the targets of {@code "sis:identifier"} and {@code "sis:geometry"} links.
+ * Values are the items having this dependency. Many items may share the same dependency.
+ *
+ * <p>At first, this map is populated without checking if the properties requested by the user contains
+ * those dependencies. After all user's requested properties have been declared to this builder, values
+ * are filtered for identifying which dependencies need to be added as implicit properties.</p>
+ */
+ private final Map<String, List<Item>> dependencies;
+
+ /**
+ * Creates a new builder instance using the default factories.
+ *
+ * @todo provides a way to specify the factories used by the data store.
+ *
+ * @param source the type from which to take the properties to keep in the projected feature.
+ * @param locale the locale to use for formatting error messages, or {@code null} for the default locale.
+ */
- public FeatureProjectionBuilder(final FeatureType source, final Locale locale) {
++ public FeatureProjectionBuilder(final DefaultFeatureType source, final Locale locale) {
+ super(null, null, locale);
+ this.source = Objects.requireNonNull(source);
+ requested = new ArrayList<>();
+ dependencies = new HashMap<>();
+ reservedNames = new HashMap<>();
+ }
+
+ /**
+ * Returns the type of features that provide the values to store in the projected features.
+ * This is the type given at construction time, except when following a XPath such as
+ * {@code "a/b/c"} in which case it may temporarily be the leaf {@code "c"} type.
+ *
+ * @return the current source of properties (never {@code null}).
+ */
- public FeatureType source() {
++ public DefaultFeatureType source() {
+ return source;
+ }
+
+ /**
+ * Returns the expected type of the given expression using the given feature type as the source.
+ * This method temporarily sets the {@linkplain #source() source} to the given {@code childType},
+ * then returns the value of {@code expression.expectedType(this)}.
+ * This is used for the last expression in a XPath such as {@code "a/b/c"}.
+ *
+ * @param childType the feature type to use.
+ * @param expression the expression from which to get the expected type.
+ * @return the expected type, or {@code null}.
+ *
+ * @see FeatureExpression#expectedType(FeatureProjectionBuilder)
+ */
- public Item using(final FeatureType childType, final FeatureExpression<?,?> expression) {
- final FeatureType previous = source;
++ public Item using(final DefaultFeatureType childType, final FeatureExpression<?,?> expression) {
++ final DefaultFeatureType previous = source;
+ final boolean status = sourceIsDependency;
+ try {
+ sourceIsDependency = true;
+ source = Objects.requireNonNull(childType);
+ return expression.expectedType(this);
+ } finally {
+ source = previous;
+ sourceIsDependency = status;
+ }
+ }
+
+ /**
+ * Adds the given property, replacing operation by an attribute storing the operation result.
+ * This method may return {@code null} if it cannot resolve the property type, in which case
+ * the caller should throw an exception (throwing an exception is left to the caller because
+ * it can produces a better error message). Operation's dependencies, if any, are added into
+ * the given {@code deferred} list.
+ *
+ * @param property the {@linkplain #source} property to add.
+ * @param deferred where to add operation's dependencies, or {@code null} for not collecting dependencies.
+ * @return builder for the projected property, or {@code null} if it cannot be resolved.
+ */
- private PropertyTypeBuilder addPropertyResult(PropertyType property, final List<String> deferred) {
- if (property instanceof Operation) {
++ private PropertyTypeBuilder addPropertyResult(AbstractIdentifiedType property, final List<String> deferred) {
++ if (property instanceof AbstractOperation) {
+ final GenericName name = property.getName();
+ do {
+ if (deferred != null) {
+ if (property instanceof AbstractOperation) {
+ deferred.addAll(((AbstractOperation) property).getDependencies());
+ } else {
+ /*
+ * Cannot resolve dependencies. Current implementation assumes that there is no dependency.
+ * Note: we could conservatively add all properties as dependencies, but this is difficult
+ * to implement efficiently.
+ */
+ }
+ }
- final IdentifiedType result = ((Operation) property).getResult();
- if (result != property && result instanceof PropertyType) {
- property = (PropertyType) result;
- } else if (result instanceof FeatureType) {
- return addAssociation((FeatureType) result).setName(name);
++ final AbstractIdentifiedType result = ((AbstractOperation) property).getResult();
++ if (result != property && result instanceof AbstractIdentifiedType) {
++ property = result;
++ } else if (result instanceof DefaultFeatureType) {
++ return addAssociation((DefaultFeatureType) result).setName(name);
+ } else {
+ return null;
+ }
- } while (property instanceof Operation);
++ } while (property instanceof AbstractOperation);
+ return addProperty(property).setName(name);
+ }
+ return addProperty(property);
+ }
+
+ /**
+ * Adds a property from the source feature type. The given property should be the result of a call to
+ * {@code source().getProperty(sourceName)}. The call to {@code getProperty(…)} is left to the caller
+ * because some callers need to wrap that call in a {@code try} block.
+ *
+ * @param property the property type, usually as one of the properties of {@link #source()}.
+ * @param named whether the {@code property} name can be used as a default name.
+ * @return handler for the given item, or {@code null} if the given property cannot be resolved.
+ */
- public Item addSourceProperty(final PropertyType property, final boolean named) {
++ public Item addSourceProperty(final AbstractIdentifiedType property, final boolean named) {
+ if (property == null) {
+ return null;
+ }
+ final PropertyTypeBuilder builder;
+ List<String> deferred;
+ if (sourceIsDependency) {
+ /*
+ * Adding a property which is not defined in the feature type specified at construction time,
+ * but which is defined at the tip of some XPath such as "a/b/c". This is not the same thing
+ * as adding an association. This is rather adding a subset of an association. We do not add
+ * dependency information because the dependencies are not directly in the source feature.
+ */
+ reserve(property.getName(), null);
+ deferred = new ArrayList<>();
+ builder = addPropertyResult(property, deferred);
+ } else {
+ /*
+ * For link operations, remember the dependencies in order to determine (after we added all properties)
+ * if we can keep the property as an operation or if we will need to copy the value in an attribute.
+ * For other kind of operations, unconditionally replace the operation by its result.
+ */
+ deferred = Features.getLinkTargets(property);
+ if (deferred.isEmpty()) {
+ deferred = new ArrayList<>();
+ builder = addPropertyResult(property, deferred);
+ } else {
+ builder = addProperty(property);
+ }
+ }
+ final var item = new Item(named ? property.getName() : null, builder);
+ requested.add(item);
+ for (String dependency : deferred) {
+ dependencies.computeIfAbsent(dependency, (key) -> new ArrayList<>(2)).add(item);
+ }
+ return item;
+ }
+
+ /**
+ * Adds a property created by the caller rather than extracted from the source feature.
+ * The given builder should have been created by a method of the {@link FeatureTypeBuilder} parent class.
+ * The name of the builder is usually not the name of a property in the {@linkplain #source() source} feature.
+ *
+ * <h4>Assertions</h4>
+ * This method verifies that the given builder is a member of the {@linkplain #properties() properties} collection.
+ * It also verifies that no {@link Item} have been created for that builder yet.
+ * For performance reasons, those verifications are performed only if assertions are enabled.
+ *
+ * @param builder builder for the computed property, or {@code null}.
+ * @param named whether the {@code builder} name can be used as a default name.
+ * @return handler for the given item, or {@code null} if the given builder was null.
+ */
+ @ArgumentCheckByAssertion
+ public Item addComputedProperty(final PropertyTypeBuilder builder, final boolean named) {
+ if (builder == null) {
+ return null;
+ }
+ assert properties().contains(builder) : builder;
+ assert requested.stream().noneMatch((item) ->item.builder() == builder) : builder;
+ final var item = new Item(named ? builder.getName() : null, builder);
+ requested.add(item);
+ return item;
+ }
+
+ /**
+ * Handler for a property inherited from the source feature type. The property is initially unnamed.
+ * A name can be specified explicitly after construction by a call to {@link #setName(GenericName)}.
+ * If no name is specified, the default name will be the same as in the source feature type if that
+ * name is available, or a default name otherwise.
+ */
+ public final class Item {
+ /**
+ * The name that the property had in the {@linkplain #source() source} feature, or {@code null}.
+ * The property built by the {@linkplain #builder} will often have the same name, but not always.
+ */
+ final GenericName sourceName;
+
+ /**
+ * The builder for configuring the property.
+ */
+ private PropertyTypeBuilder builder;
+
+ /**
+ * Whether this item got an explicit name. The specified name may be
+ * identical to the name in the {@linkplain #source() source} feature.
+ */
+ private boolean isNamed;
+
+ /**
+ * Whether to keep the current name if it is available. This is set to {@code true} when user did not
+ * specified explicitly a name, but keeping the name of the source property would be a natural choice.
+ * However, before to use that name, we need to wait and see if that name will be explicitly used for
+ * another property.
+ */
+ private boolean preferCurrentName;
+
+ /**
+ * Whether this property needs at least one dependency which is not included in the list of properties
+ * requested by the user. In such case, we cannot keep the link operation and need to replace the link
+ * by a stored attribute.
+ *
+ * @see #replaceIfMissingDependency()
+ */
+ private boolean hasMissingDependency;
+
+ /**
+ * Expression for evaluating the attribute value from a source feature instance, or {@code null} if none.
+ * This field should be non-null only if the value will be stored in an attribute. If the property is an
+ * operation, then this field should be null (this is not the expression of the operation).
+ *
+ * @see #attributeValueGetter()
+ */
- private Expression<? super Feature, ?> attributeValueGetter;
++ private Expression<? super AbstractFeature, ?> attributeValueGetter;
+
+ /**
+ * Creates a new handle for the property created by the given builder.
+ *
+ * @param sourceName the property name in the {@linkplain #source() source} feature, or {@code null}.
+ * @param builder the builder for configuring the property.
+ */
+ private Item(final GenericName sourceName, final PropertyTypeBuilder builder) {
+ this.sourceName = sourceName;
+ this.builder = builder;
+ }
+
+ /**
+ * Returns a string representation for debugging purposes.
+ */
+ @Override
+ public String toString() {
+ return Strings.toString(getClass(),
+ "sourceName", (sourceName != null) ? sourceName.toString() : null,
+ "targetName", isNamed ? getName() : null,
+ "valueClass", (builder instanceof AttributeTypeBuilder<?>) ? ((AttributeTypeBuilder<?>) builder).getValueClass() : null,
+ null, hasMissingDependency ? "hasMissingDependency" : null);
+ }
+
+ /**
+ * Returns the property type builder wrapped by this item.
+ * The following operations are allowed on the returned builder:
+ *
+ * <ul>
+ * <li>Set the cardinality (minimum and maximum occurrences).</li>
+ * <li>Build the {@code PropertyType}.</li>
+ * </ul>
+ *
+ * The following operations should <em>not</em> be executed on the returned builder.
+ * Use the dedicated methods in this class instead:
+ *
+ * <ul>
+ * <li>Set the name: use {@link #setName(GenericName)}.</li>
+ * <li>Set the value class: use {@link #replaceValueClass(UnaryOperator)}.</li>
+ * </ul>
+ *
+ * @return the property type builder wrapped by this item.
+ */
+ public PropertyTypeBuilder builder() {
+ hasModifiedProperties = true; // Conservative because the caller may do anything on the builder.
+ return builder;
+ }
+
+ /**
+ * Replaces this property by a stored attribute if at least one dependency is not in the list of properties
+ * requested by the user. This method should be invoked only for preparing the user requested feature type.
+ * This method should not be invoked for preparing the feature type with dependencies, because the latter
+ * should contain the missing dependencies.
+ */
+ private void replaceIfMissingDependency() {
+ if (hasMissingDependency) {
+ hasMissingDependency = false;
+ hasModifiedProperties = true;
+ final var old = builder;
+ builder = addPropertyResult(old.build(), null); // `old.build()` returns the existing operation.
+ old.replaceBy(builder);
+ }
+ }
+
+ /**
+ * Sets the class of attribute values. If the builder is an instance of {@link AttributeTypeBuilder}
+ * and if {@code type.apply(valueClass)} returns a non-null value ({@code valueClass} is the current
+ * class of attribute values), then this method sets the new attribute value class to the specified
+ * type and returns {@code true}. Otherwise, this method returns {@code false}.
+ *
+ * @param type a converter from current class to the new class of attribute values.
+ * @return whether the value class has been set to the value returned by {@code type}.
+ * @throws UnconvertibleObjectException if the default value cannot be converted to the given type.
+ */
+ public boolean replaceValueClass(final UnaryOperator<Class<?>> type) {
+ if (builder instanceof AttributeTypeBuilder<?>) {
+ final var ab = (AttributeTypeBuilder<?>) builder;
+ final Class<?> r = type.apply(ab.getValueClass());
+ if (r != null) {
+ if (builder != (builder = ab.setValueClass(r))) {
+ hasModifiedProperties = true;
+ }
+ return true;
+ }
+ } else if (builder instanceof AssociationRoleBuilder) {
+ // We do not yet have a special case for this one.
+ } else {
+ final var property = builder.build();
- if (property instanceof Operation) {
++ if (property instanceof AbstractOperation) {
+ /*
+ * Less common case where the caller wants to change the type of an operation.
+ * We cannot change the type of an operation (unless we replace the operation
+ * by a stored attribute). Therefore, we only check type compatibility.
+ */
- final var result = ((Operation) property).getResult();
- if (result instanceof AttributeType<?>) {
- final Class<?> c = ((AttributeType<?>) result).getValueClass();
++ final var result = ((AbstractOperation) property).getResult();
++ if (result instanceof DefaultAttributeType<?>) {
++ final Class<?> c = ((DefaultAttributeType<?>) result).getValueClass();
+ final Class<?> r = type.apply(c);
+ if (r != null) {
+ // We can be lenient for link operation, but must be strict for other operations.
+ if (Features.getLinkTarget(property).isPresent() ? r.isAssignableFrom(c) : r.equals(c)) {
+ return true;
+ }
+ throw new UnconvertibleObjectException(Errors.forLocale(getLocale())
+ .getString(Errors.Keys.CanNotConvertFromType_2, c, r));
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Sets the expression to use for evaluating the property value.
+ * If {@code stored} is {@code true} (the usual case), then the expression will be evaluated early
+ * and its result will be stored as an attribute value, unless this property is not an attribute.
+ * If {@code stored} is {@code false}, this method replaces the attribute by an operation wrapping
+ * the given expression. In other words, the evaluation of the expression will be deferred.
+ * The latter case is possible only if the {@code FeatureType} contains all dependencies
+ * that the operation needs.
+ *
+ * @param expression the expression to be evaluated by the operation.
+ */
- public void setValueGetter(final Expression<? super Feature, ?> expression, final boolean stored) {
++ public void setValueGetter(final Expression<? super AbstractFeature, ?> expression, final boolean stored) {
+ if (builder instanceof AttributeTypeBuilder<?>) {
+ if (stored) {
+ attributeValueGetter = expression;
+ } else {
+ final var atb = (AttributeTypeBuilder<?>) builder;
+ /*
+ * Optimization: we could compute `storedType = atb.build()` unconditionally,
+ * which creates an attribute with the final name in the target feature type.
+ * However, in the particular case of links, we are better to use the name of
+ * the property in the source feature type, because it allows an optimization
+ * in `ExpressionOperation.create(…)` (a replacement by a `LinkOperation`).
+ */
- AttributeType<?> storedType = null;
++ DefaultAttributeType<?> storedType = null;
+ if (expression instanceof ValueReference<?,?>) {
+ var candidate = source.getProperty(((ValueReference<?,?>) expression).getXPath());
- if (candidate instanceof AttributeType<?>) {
- storedType = (AttributeType<?>) candidate;
++ if (candidate instanceof DefaultAttributeType<?>) {
++ storedType = (DefaultAttributeType<?>) candidate;
+ }
+ }
+ if (storedType == null) {
+ storedType = atb.build(); // Same name as in the `identification` map below.
+ }
+ final var identification = Map.of(AbstractOperation.NAME_KEY, builder.getName());
+ builder = addProperty(FeatureOperations.expression(identification, expression, storedType));
+ atb.replaceBy(builder);
+ hasModifiedProperties = true;
+ }
+ } else {
+ // The property is an operation, usually a link. Leave it as-is.
+ }
+ }
+
+ /**
+ * Returns the expression for evaluating the value to store in the attribute built by this item.
+ * The expression may be {@code null} if the value is computed on-the-fly (i.e. the property is
+ * an operation), or if the expression has not been specified.
+ */
- final Expression<? super Feature, ?> attributeValueGetter() {
++ final Expression<? super AbstractFeature, ?> attributeValueGetter() {
+ return attributeValueGetter;
+ }
+
+ /**
+ * Sets the coordinate reference system that characterizes the values of this attribute.
+ *
+ * @param crs coordinate reference system associated to attribute values, or {@code null}.
+ * @return {@code this} for method calls chaining.
+ */
+ public Item setCRS(final CoordinateReferenceSystem crs) {
+ if (builder instanceof AttributeTypeBuilder<?>) {
+ builder = ((AttributeTypeBuilder<?>) builder).setCRS(crs);
+ hasModifiedProperties = true;
+ }
+ return this;
+ }
+
+ /**
+ * Returns whether the property built by this item is equivalent to the given property.
+ * The caller should have verified that {@link #hasModifiedProperties} is {@code false}
+ * before to invoke this method, because the implementation performs a filtering based
+ * on the property name only. This is that way for accepting differences in metadata.
+ *
+ * @param property the property to compare.
+ * @return whether this item builds a property equivalent to the given one.
+ *
+ * @see #isIdentity()
+ */
- private boolean equivalent(final PropertyType property) {
++ private boolean equivalent(final AbstractIdentifiedType property) {
+ return builder.getName().equals(property.getName());
+ }
+
+ /**
+ * Returns the name of the projected property.
+ * This is initially the name of the property given at construction time,
+ * but can be changed later by a call to {@link #setName(GenericName)}.
+ *
+ * @return the name of the projected property.
+ */
+ public String getName() {
+ return builder.getName().toString();
+ }
+
+ /**
+ * Sets the name of the projected property. A {@code null} argument means that the name is unspecified,
+ * in which case a different name may be generated later if the current name collides with other names.
+ *
+ * <p>This method should be invoked exactly once for each item, even if the argument is {@code null}.
+ * The reason is because this method uses this information for recording which names to reserve.</p>
+ *
+ * @param targetName the desired name in the projected feature, or {@code null} if unspecified.
+ */
+ public void setName(final GenericName targetName) {
+ if (targetName == null) {
+ reserve(sourceName, null); // Will use that name only if not owned by another item.
+ preferCurrentName = true;
+ } else if (targetName.equals(sourceName)) {
+ reserve(sourceName, this); // Take possession of that name.
+ isNamed = true;
+ } else {
+ builder.setName(targetName);
+ reserve(targetName, this);
+ hasModifiedProperties = true; // Because the name is different.
+ isNamed = true;
+ }
+ }
+
+ /**
+ * If this item has not received an explicit name, infers a default name.
+ * This method should be invoked only after {@link #setName(GenericName)}
+ * has been invoked for all items, for allowing this class to know which
+ * names are reserved.
+ */
+ private void validateName() {
+ if (!isNamed) {
+ final Item owner = reservedNames.get(sourceName);
+ if (owner != this) {
+ GenericName name = sourceName;
+ if (owner != null || name == null || (!preferCurrentName && reservedNames.containsKey(name))) {
+ do {
+ var text = Vocabulary.formatInternational(Vocabulary.Keys.Unnamed_1, ++unnamedNumber);
+ name = builder.setName(text).getName(); // Local name with the appropriate name space.
+ } while (reservedNames.containsKey(name)); // Reminder: the associated value may be null.
+ }
+ reserve(name, this);
+ }
+ isNamed = true;
+ }
+ }
+ }
+
+ /**
+ * Declares the given name as reserved. If this class needs to generate a default name,
+ * it will ensure that automatically generated names do not conflict with reserved names.
+ *
+ * @param name name to reserve for a projected property type, or {@code null} if none.
+ * @param owner the builder using that name, or {@code null} if none.
+ */
+ private void reserve(GenericName name, final Item owner) {
+ if (name != null) {
+ // By `putIfAbsent` method contract, non-null values have precedence over null values.
+ reservedNames.putIfAbsent(name, owner);
+ if (name != (name = name.tip())) { // Shortcut for a majority of cases.
+ reservedNames.putIfAbsent(name, owner);
+ }
+ }
+ }
+
+ /**
+ * Adds dependencies. This method adds in the {@code deferred} list any transitive
+ * dependencies which may need to be added in a second pass after this method call.
+ * The elements added into {@code deferred} are {@linkplain #source} properties.
+ *
+ * @param deferred where to add missing transitive dependencies (source properties).
+ */
- private void resolveDependencies(final List<PropertyType> deferred) {
++ private void resolveDependencies(final List<AbstractIdentifiedType> deferred) {
+ final var it = dependencies.entrySet().iterator();
+ while (it.hasNext()) {
+ final Map.Entry<String, List<Item>> entry = it.next();
- final PropertyType property = source.getProperty(entry.getKey());
++ final AbstractIdentifiedType property = source.getProperty(entry.getKey());
+ final GenericName sourceName = property.getName();
+ Item item = reservedNames.get(sourceName);
+ if (item != null) {
+ if (!sourceName.equals(item.sourceName)) {
+ /*
+ * If we want to support that feature in a future version, we would need a `replace` method
+ * for replacing a builder at a specific index or for a specific property name. A difficulty
+ * is that for compound identifiers, we have no API for reusing the same prefix and suffix.
+ */
+ throw new UnsupportedOperationException("Renaming of properties used in links is not yet supported.");
+ }
+ } else {
+ for (Item dependent : entry.getValue()) {
+ dependent.hasMissingDependency = true;
+ }
+ deferred.add(property);
+ }
+ it.remove();
+ }
+ }
+
+ /**
+ * Returns {@code true} if the feature to be built should be equivalent to the source feature.
+ *
+ * @return whether the {@linkplain #source} feature type can be used directly.
+ */
+ private boolean isIdentity() {
+ if (hasModifiedProperties) {
+ return false;
+ }
+ final Iterator<Item> it = requested.iterator();
- for (PropertyType property : source.getProperties(true)) {
++ for (AbstractIdentifiedType property : source.getProperties(true)) {
+ if (!(it.hasNext() && it.next().equivalent(property))) {
+ return false;
+ }
+ }
+ return !it.hasNext();
+ }
+
+ /**
+ * Returns the feature type described by this builder. This method may return the
+ * {@linkplain #source() source} directly if this projection performs no operation.
+ */
+ @Override
- public FeatureType build() {
++ public DefaultFeatureType build() {
+ return isIdentity() ? source : super.build();
+ }
+
+ /**
+ * Sets the default name of all anonymous properties, then builds the feature types.
+ * Two feature types are built: one with only the requested properties, and another
+ * type augmented with dependencies of operations such as links.
+ *
+ * <p>This method should be invoked exactly once.</p>
+ *
+ * <h4>Identity operation</h4>
+ * If the result is a feature type with all the properties of the source feature,
+ * with the same property names in the same order, and if the expressions are only
+ * fetching the values (no computation), then this method returns an empty value
+ * for meaning that this projection does nothing.
+ *
+ * @return the feature types with and without dependencies, or empty if there is no projection.
+ */
+ public Optional<FeatureProjection> project() {
+ requested.forEach(Item::validateName);
+ /*
+ * Add properties for all dependencies that are required by link operations but are not already present.
+ * If there is no need to add anything, `typeWithDependencies` will be directly the feature type to return.
+ */
+ final List<PropertyTypeBuilder> properties = properties();
+ final int count = properties.size();
- final var deferred = new ArrayList<PropertyType>();
++ final var deferred = new ArrayList<AbstractIdentifiedType>();
+ resolveDependencies(deferred);
+ /*
+ * If there is no dependencies, the requested type and the type with dependencies are the same.
+ * Otherwise, we need to resolve transitive dependencies before to build each type.
+ */
- final FeatureType typeRequested, typeWithDependencies;
++ final DefaultFeatureType typeRequested, typeWithDependencies;
+ if (deferred.isEmpty()) {
+ typeRequested = typeWithDependencies = build();
+ } else {
+ do {
- for (PropertyType property : deferred) {
++ for (AbstractIdentifiedType property : deferred) {
+ final Item item = addSourceProperty(property, true);
+ if (item != null) {
+ item.validateName();
+ item.setValueGetter(FeatureOperations.expressionOf(property), true);
+ }
+ }
+ deferred.clear();
+ resolveDependencies(deferred);
+ } while (!deferred.isEmpty());
+ typeWithDependencies = build();
+ properties.subList(count, properties.size()).clear(); // Keep only the properties requested by user.
+ requested.forEach(Item::replaceIfMissingDependency);
+ typeRequested = build();
+ }
+ if (source.equals(typeRequested) && source.equals(typeWithDependencies)) {
+ return Optional.empty();
+ }
+ return Optional.of(new FeatureProjection(typeRequested, typeWithDependencies, requested));
+ }
+ }
diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureView.java
index 0000000000,a0dac0a57f..f8c5a97a49
mode 000000,100644..100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureView.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureView.java
@@@ -1,0 -1,131 +1,129 @@@
+ /*
+ * 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.privy;
+
+ import org.apache.sis.feature.AbstractFeature;
+
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.Property;
++// Specific to the main branch:
++import org.apache.sis.feature.DefaultFeatureType;
+
+
+ /**
+ * A feature containing a subset of the properties of another feature.
+ * The feature type of the view must be specified in argument together with the source feature instance.
+ * All properties that are present in this feature view shall have the same name as in the source feature.
+ * This class does not verify that requirement.
+ *
+ * <h2>Limitations</h2>
+ * For performance and simplicity reasons, the current implementation does not prevent users
+ * from requesting a property that exists in the full feature instance but not in this subset.
+ *
+ * <h2>Possible evolution</h2>
+ * It would be possible for the view to contain operations that are not present in the source features.
+ * This extension has not yet been implemented because not yet needed.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ */
+ final class FeatureView extends AbstractFeature {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = -4299168913599597991L;
+
+ /**
+ * The instance with all properties.
+ */
+ @SuppressWarnings("serial") // Apache SIS implementations are serializable.
- private final Feature source;
++ private final AbstractFeature source;
+
+ /**
+ * Creates a new feature instance which is a subset of the given {@code source} feature.
+ * This constructor does not verify the consistency of the two arguments.
+ *
+ * @param subset the feature type that describes the feature subset.
+ * @param source the complete feature instance to view as a subset.
+ */
- FeatureView(final FeatureType subset, final Feature source) {
++ FeatureView(final DefaultFeatureType subset, final AbstractFeature source) {
+ super(subset);
+ this.source = source;
+ }
+
+ /**
+ * Returns the property (attribute, feature association or operation result) of the given name.
+ * This method delegates to the wrapped source without checking whether the given name exists in this subset.
+ *
+ * @param name the property name.
+ * @return the property of the given name (never {@code null}).
+ */
+ @Override
- public Property getProperty(final String name) {
++ public Object getProperty(final String name) {
+ return source.getProperty(name);
+ }
+
+ /**
+ * Sets the property (attribute or feature association).
+ * This method delegates to the wrapped source without checking whether the given name exists in this subset.
+ *
+ * @param property the property to set.
+ */
+ @Override
- public void setProperty(final Property property) {
++ public void setProperty(final Object property) {
+ source.setProperty(property);
+ }
+
+ /**
+ * Returns the value for the property of the given name.
+ * This method delegates to the wrapped source without checking whether the given name exists in this subset.
+ *
+ * @param name the property name.
+ * @return value of the specified property, or the default value (which may be {@code null}} if none.
+ */
+ @Override
+ public Object getPropertyValue(final String name) {
+ return source.getPropertyValue(name);
+ }
+
+ /**
+ * Sets the value for the property of the given name.
+ * This method delegates to the wrapped source without checking whether the given name exists in this subset.
+ *
+ * @param name the property name.
+ * @param value the new value for the specified property (may be {@code null}).
+ */
+ @Override
+ public void setPropertyValue(final String name, final Object value) {
+ source.setPropertyValue(name, value);
+ }
+
+ /**
+ * Returns the value for the property of the given name if that property exists, or a fallback value otherwise.
+ * This method delegates to the wrapped source without checking whether the given name exists in this subset.
+ *
+ * <h4>Design note</h4>
+ * We could add a verification of whether the property exists in the feature type given by {@link #getType()}.
+ * We don't do that for now because the current usages of this method in the Apache SIS code base do not need
+ * this method to be strict, and for consistency with the behavior of other methods in this class.
+ *
+ * @param name the property name.
+ * @param missingPropertyFallback the value to return if no attribute or association of the given name exists.
+ * @return value or default value of the specified property, or {@code missingPropertyFallback}.
+ */
+ @Override
+ public Object getValueOrFallback(final String name, final Object missingPropertyFallback) {
+ return source.getValueOrFallback(name, missingPropertyFallback);
+ }
+ }
diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ArithmeticFunction.java
index cddc6af90c,eabd8cdf58..1ad4f4f944
--- 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
@@@ -27,9 -26,9 +26,8 @@@ import org.apache.sis.util.Unconvertibl
import org.apache.sis.util.resources.Errors;
import org.apache.sis.math.Fraction;
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.AttributeType;
-import org.opengis.filter.Expression;
+// Specific to the main branch:
- import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.DefaultAttributeType;
/**
diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/AssociationValue.java
index 8e44067fff,1ab716a0a3..43b9a49c15
--- 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
@@@ -22,18 -22,17 +22,17 @@@ import java.util.List
import java.util.Collection;
import java.util.Optional;
import org.apache.sis.feature.Features;
- import org.apache.sis.feature.builder.FeatureTypeBuilder;
- import org.apache.sis.feature.builder.PropertyTypeBuilder;
+ import org.apache.sis.feature.privy.FeatureProjectionBuilder;
import org.apache.sis.math.FunctionProperty;
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-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;
+// Specific to the main branch:
+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.pending.geoapi.filter.Name;
+import org.apache.sis.pending.geoapi.filter.ValueReference;
/**
@@@ -207,32 -201,33 +206,33 @@@ walk: if (specifiedType != null) try
}
/**
- * Provides the expected type of values produced by this expression when a feature of the given type is evaluated.
+ * Provides the expected type of values produced by this expression. This method delegates to
+ * {@link #accessor} after setting the source feature type to the tip of {@code "a/b/c"} path.
*
- * @param valueType the type of features to be evaluated by the given expression.
- * @param addTo where to add the type of properties evaluated by the given expression.
+ * @param addTo where to add the type of properties evaluated by this expression.
* @return builder of the added property, or {@code null} if this method cannot add a property.
- * @throws IllegalArgumentException if this method cannot determine the property type for the given feature type.
- * @throws PropertyNotFoundException if the property was not found in {@code addTo.source()}.
++ * @throws IllegalArgumentException if the property was not found in {@code addTo.source()}.
*/
@Override
- public PropertyTypeBuilder expectedType(DefaultFeatureType valueType, final FeatureTypeBuilder addTo) {
+ public FeatureProjectionBuilder.Item expectedType(final FeatureProjectionBuilder addTo) {
- FeatureType valueType = addTo.source();
++ DefaultFeatureType valueType = addTo.source();
for (final String p : path) {
- final PropertyType type;
+ final AbstractIdentifiedType type;
try {
type = valueType.getProperty(p);
- } catch (PropertyNotFoundException e) {
+ } catch (IllegalArgumentException e) {
if (accessor.isVirtual) {
// The association does not exist but may be defined on a yet unknown child type.
- return accessor.expectedType(addTo);
+ return accessor.defaultType(addTo);
}
throw e;
}
- if (!(type instanceof FeatureAssociationRole)) {
+ if (!(type instanceof DefaultAssociationRole)) {
return null;
}
- valueType = ((FeatureAssociationRole) type).getValueType();
+ valueType = ((DefaultAssociationRole) type).getValueType();
}
- return accessor.expectedType(valueType, addTo);
+ return addTo.using(valueType, accessor);
}
/**
diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ConvertFunction.java
index 3275c03e5d,c614215ed9..354bac4286
--- 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/ConvertFunction.java
@@@ -30,9 -28,9 +28,6 @@@ import org.apache.sis.feature.privy.Fea
import org.apache.sis.math.FunctionProperty;
import org.apache.sis.util.resources.Errors;
- // Specific to the main branch:
- import org.apache.sis.feature.DefaultFeatureType;
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
--
/**
* Expression whose results are converted to a different type.
diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java
index 03264883a2,9133bec01e..1c39a4bd3b
--- 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
@@@ -32,13 -33,16 +33,12 @@@ import org.apache.sis.filter.sqlmm.Regi
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.iso.AbstractFactory;
import org.apache.sis.util.resources.Errors;
--import org.apache.sis.util.privy.Strings;
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.util.Iterator;
-import java.time.Instant;
-import org.opengis.filter.*;
-import org.opengis.feature.Feature;
-import org.opengis.filter.capability.AvailableFunction;
-import org.opengis.filter.capability.FilterCapabilities;
-import org.apache.sis.util.privy.AbstractMap;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.pending.geoapi.filter.MatchAction;
+import org.apache.sis.pending.geoapi.filter.SpatialOperatorName;
+import org.apache.sis.pending.geoapi.filter.DistanceOperatorName;
/**
@@@ -142,8 -139,44 +142,32 @@@ public abstract class DefaultFilterFact
return Features.DEFAULT;
}
+ /**
+ * Returns a factory operating on resource instances of the given class.
+ * The current implementation recognizes the following classes:
+ *
+ * <ul>
+ * <li>{@link Feature}: delegate to {@link #forFeatures()}.</li>
+ * </ul>
+ *
+ * More classes may be added in future versions.
+ *
+ * @param <R> compile-time value of the {@code type} argument.
+ * @param type type of resources that the factory shall accept.
+ * @return factory operating on resource instances of the given class.
+ * @since 1.5
+ */
+ @SuppressWarnings("unchecked")
- public static <R> Optional<FilterFactory<R, Object, Object>> forResources(final Class<R> type) {
- if (type.equals(Feature.class)) {
- return Optional.of((FilterFactory<R, Object, Object>) forFeatures());
++ public static <R> Optional<DefaultFilterFactory<R, Object, Object>> forResources(final Class<R> type) {
++ if (type.equals(AbstractFeature.class)) {
++ return Optional.of((DefaultFilterFactory<R, Object, Object>) forFeatures());
+ } else {
+ return Optional.empty();
+ }
+ }
+
/**
- * Describes the abilities of this factory. The description includes restrictions on
- * the available spatial operations, scalar operations, lists of supported functions,
- * and description of which geometry literals are understood.
- *
- * @return description of the abilities of this factory.
- */
- @Override
- public FilterCapabilities getCapabilities() {
- return new Capabilities(this); // Cheap to construct, no need to cache.
- }
-
- /**
- * A filter factory operating on {@link Feature} instances.
+ * A filter factory operating on {@link AbstractFeature} instances.
*
* @param <G> base class of geometry objects. The implementation-neutral type is GeoAPI {@link Geometry},
* but this factory allows the use of other implementations such as JTS
diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/InvalidXPathException.java
index 0000000000,3a49222670..86379fdf59
mode 000000,100644..100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/InvalidXPathException.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/InvalidXPathException.java
@@@ -1,0 -1,81 +1,78 @@@
+ /*
+ * 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;
+
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.InvalidFilterValueException;
-
+
+ /**
+ * Exceptions thrown when the XPath in an expression is invalid or unsupported.
+ * Apache SIS currently supports only a small subset of XPath syntax, mostly paths of
+ * the form {@code "a/b/c"} (and not everywhere) and the {@code "Q{namespace}"} syntax.
+ *
+ * <h4>Relationship with standard libraries</h4>
+ * The standard Java libraries provides a {@link javax.xml.xpath.XPathException}.
+ * This {@code InvalidXPathException} differs in that it is an unchecked exception
+ * and is thrown in the context of operations with OGC filters and expressions.
+ * In some implementations, {@code InvalidXPathException} may have a {@code XPathException} as its cause.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.5
+ *
+ * @see javax.xml.xpath.XPathException
+ *
+ * @since 1.5
+ */
-public class InvalidXPathException extends InvalidFilterValueException {
++public class InvalidXPathException extends IllegalArgumentException {
+ /**
+ * Serial number for inter-operability with different versions.
+ */
+ private static final long serialVersionUID = 1654277877397802378L;
+
+ /**
+ * Creates an exception with no message.
+ */
+ public InvalidXPathException() {
+ super();
+ }
+
+ /**
+ * Creates an exception with the specified message.
+ *
+ * @param message the detail message, saved for later retrieval by the {@link #getMessage()} method.
+ */
+ public InvalidXPathException(final String message) {
+ super(message);
+ }
+
+ /**
+ * Creates an exception with the specified cause.
+ *
+ * @param cause the cause, saved for later retrieval by the {@link #getCause()} method.
+ */
+ public InvalidXPathException(final Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * Creates an exception with the specified message and cause.
+ *
+ * @param message the detail message, saved for later retrieval by the {@link #getMessage()} method.
+ * @param cause the cause, saved for later retrieval by the {@link #getCause()} method.
+ */
+ public InvalidXPathException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+ }
diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LeafExpression.java
index a502cbb779,a869d79ae4..0c9958f264
--- 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
@@@ -27,14 -27,13 +27,12 @@@ import org.apache.sis.util.iso.Names
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.collection.WeakValueHashMap;
import org.apache.sis.feature.privy.FeatureExpression;
+ import org.apache.sis.feature.privy.FeatureProjectionBuilder;
import org.apache.sis.filter.internal.Node;
- import org.apache.sis.feature.builder.FeatureTypeBuilder;
- import org.apache.sis.feature.builder.PropertyTypeBuilder;
import org.apache.sis.math.FunctionProperty;
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.AttributeType;
-import org.opengis.filter.Expression;
+// Specific to the main branch:
- import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.DefaultAttributeType;
/**
@@@ -154,16 -149,17 +152,17 @@@ abstract class LeafExpression<R,V> exte
}
/**
- * Provides the type of values returned by {@link #apply(Object)}
- * wrapped in an {@link DefaultAttributeType} named "Literal".
+ * Provides the type of values returned by {@link #apply(Object)}.
- * The returned item wraps an {@link AttributeType} named "Literal".
++ * The returned item wraps an {@code AttributeType} named "Literal".
+ * The attribute type is determined by the class of the {@linkplain #value}.
*
* @param addTo where to add the type of properties evaluated by the given expression.
- * @return builder of the added property.
+ * @return handler for the added property.
*/
@Override
- public PropertyTypeBuilder expectedType(DefaultFeatureType ignored, final FeatureTypeBuilder addTo) {
+ public FeatureProjectionBuilder.Item expectedType(final FeatureProjectionBuilder addTo) {
final Class<?> valueType = getValueClass();
- AttributeType<?> propertyType;
+ DefaultAttributeType<?> propertyType;
synchronized (TYPES) {
propertyType = TYPES.get(valueType);
if (propertyType == null) {
diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java
index 9fc8e48551,06410e94c4..489c777fbd
--- 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
@@@ -29,15 -27,13 +27,14 @@@ import org.apache.sis.feature.privy.Fea
import org.apache.sis.filter.privy.XPath;
import org.apache.sis.util.resources.Errors;
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.PropertyNotFoundException;
-import org.opengis.filter.ValueReference;
+// Specific to the main branch:
+import org.opengis.util.ScopedName;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractIdentifiedType;
- import org.apache.sis.feature.AbstractOperation;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.pending.geoapi.filter.Name;
+import org.apache.sis.pending.geoapi.filter.ValueReference;
/**
@@@ -375,21 -366,27 +375,27 @@@ abstract class PropertyValue<V> extend
/**
* Provides the expected type of values produced by this expression when a feature of the given type is evaluated.
+ * The source feature type is specified indirectly by {@link FeatureProjectionBuilder#source()}.
*
- * @param valueType the type of features to be evaluated by the given expression.
- * @param addTo where to add the type of properties evaluated by the given expression.
+ * <h4>Handling of operations</h4>
+ * Properties that are operations are replaced by attributes where the operation result will be stored.
+ * An exception to this rule is the links such as {@code "sis:identifier"} and {@code "sis:geometry"},
+ * in which case the link operation is kept. It may force {@code FeatureProjectionBuilder} to add also
+ * the dependencies (targets) of the link.
+ *
+ * @param addTo where to add the type of properties evaluated by this expression.
* @return builder of the added property, or {@code null} if this method cannot add a property.
- * @throws PropertyNotFoundException if the property was not found in {@code addTo.source()}.
+ * @throws IllegalArgumentException if this method cannot determine the property type for the given feature type.
*/
@Override
- public PropertyTypeBuilder expectedType(final DefaultFeatureType valueType, final FeatureTypeBuilder addTo) {
+ public FeatureProjectionBuilder.Item expectedType(final FeatureProjectionBuilder addTo) {
- PropertyType type;
+ AbstractIdentifiedType type;
try {
- type = valueType.getProperty(name);
+ type = addTo.source().getProperty(name);
- } catch (PropertyNotFoundException e) {
+ } catch (IllegalArgumentException e) {
if (isVirtual) {
- // The property does not exist but may be defined on a yet unknown child type.
- return expectedType(addTo);
+ // The property does not exist but may be defined in a yet unknown child type.
+ return defaultType(addTo);
}
throw e;
}
diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/FunctionWithSRID.java
index 40728be85a,2d751dd31e..82b5e59df7
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/FunctionWithSRID.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/FunctionWithSRID.java
@@@ -30,10 -28,10 +28,9 @@@ import org.apache.sis.feature.privy.Fea
import org.apache.sis.util.privy.Constants;
import org.apache.sis.util.resources.Errors;
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Literal;
-import org.opengis.filter.Expression;
-import org.opengis.filter.InvalidFilterValueException;
+// Specific to the main branch:
- import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.filter.Expression;
+import org.apache.sis.pending.geoapi.filter.Literal;
/**
diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/SpatialFunction.java
index f4f27cc967,b938087a8a..714288c7f8
--- 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
@@@ -32,9 -30,9 +30,8 @@@ import org.apache.sis.util.ArgumentChec
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.iso.Names;
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.filter.Expression;
-import org.opengis.filter.InvalidFilterValueException;
+// Specific to the main branch:
+import org.apache.sis.filter.Expression;
- import org.apache.sis.feature.DefaultFeatureType;
/**
@@@ -180,33 -178,28 +177,28 @@@ abstract class SpatialFunction<R> exten
* <li>Otherwise an attribute is created with the return value specified by the operation.</li>
* </ul>
*
- * @param valueType the type of features on which to apply this expression.
- * @param addTo where to add the type of properties evaluated by this expression.
+ * @param addTo where to add the type of properties evaluated by this expression.
* @return builder of type resulting from expression evaluation (never null).
- * @throws IllegalArgumentException if the given feature type does not contain the expected properties,
- * @throws InvalidFilterValueException if the source feature type does not contain the expected properties,
++ * @throws IllegalArgumentException if the source feature type does not contain the expected properties,
* or if this method cannot determine the result type of the expression.
* It may be because that expression is backed by an unsupported implementation.
*/
@Override
- public PropertyTypeBuilder expectedType(final DefaultFeatureType valueType, final FeatureTypeBuilder addTo) {
- AttributeTypeBuilder<?> att;
- cases: if (operation.isGeometryInOut()) {
+ public FeatureProjectionBuilder.Item expectedType(final FeatureProjectionBuilder addTo) {
+ if (operation.isGeometryInOut()) {
final FeatureExpression<?,?> fex = FeatureExpression.castOrCopy(getParameters().get(0));
if (fex != null) {
- final PropertyTypeBuilder type = fex.expectedType(valueType, addTo);
- if (type instanceof AttributeTypeBuilder<?>) {
- att = (AttributeTypeBuilder<?>) type;
- final Geometries<?> library = Geometries.factory(att.getValueClass());
- if (library != null) {
- att = att.setValueClass(operation.getReturnType(library));
- break cases;
- }
+ final FeatureProjectionBuilder.Item item = fex.expectedType(addTo);
+ final boolean success = item.replaceValueClass((c) -> {
+ final Geometries<?> library = Geometries.factory(c);
+ return (library == null) ? null : operation.getReturnType(library);
+ });
+ if (success) {
+ return item;
}
}
- throw new InvalidFilterValueException(Resources.format(Resources.Keys.NotAGeometryAtFirstExpression));
+ throw new IllegalArgumentException(Resources.format(Resources.Keys.NotAGeometryAtFirstExpression));
- } else {
- att = addTo.addAttribute(getValueClass());
}
- return att.setName(getFunctionName());
+ return addTo.addComputedProperty(addTo.addAttribute(getValueClass()).setName(getFunctionName()), false);
}
}
diff --cc endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureIterator.java
index 7ff19f8852,4636952051..83dc035fcc
--- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureIterator.java
+++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureIterator.java
@@@ -25,14 -25,14 +25,14 @@@ import java.sql.PreparedStatement
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.sis.metadata.sql.privy.SQLBuilder;
- import org.apache.sis.storage.base.FeatureProjection;
+ import org.apache.sis.feature.privy.FeatureProjection;
import org.apache.sis.util.collection.WeakValueHashMap;
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.filter.SortOrder;
-import org.opengis.filter.SortProperty;
-import org.opengis.filter.SortBy;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.pending.geoapi.filter.SortOrder;
+import org.apache.sis.pending.geoapi.filter.SortProperty;
+import org.apache.sis.pending.geoapi.filter.SortBy;
/**
diff --cc endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureStream.java
index 4e81c0694d,2386a8fa35..963613ab67
--- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureStream.java
+++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureStream.java
@@@ -41,13 -39,12 +39,12 @@@ import org.apache.sis.util.privy.String
import org.apache.sis.util.stream.DeferredStream;
import org.apache.sis.util.stream.PaginedStream;
import org.apache.sis.storage.DataStoreException;
- import org.apache.sis.storage.base.FeatureProjection;
+ import org.apache.sis.feature.privy.FeatureProjection;
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.filter.Filter;
-import org.opengis.filter.SortBy;
+// Specific to the main branch:
+import org.apache.sis.filter.Filter;
- import org.apache.sis.filter.Expression;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.pending.geoapi.filter.SortBy;
/**
diff --cc endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
index 1af3574bf7,9b7981d788..7110ac76a7
--- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
+++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
@@@ -57,9 -57,10 +57,10 @@@ import org.apache.sis.util.privy.Consta
import org.apache.sis.io.wkt.Convention;
import org.apache.sis.io.wkt.WKTFormat;
import org.apache.sis.io.wkt.Warnings;
+ import org.apache.sis.util.Workaround;
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.metadata.Identifier;
+// Specific to the main branch:
+import org.opengis.referencing.ReferenceIdentifier;
/**
diff --cc endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Table.java
index 7d53b4d7d6,1a56e7149e..118a8f666c
--- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Table.java
+++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Table.java
@@@ -43,11 -46,12 +46,10 @@@ import org.apache.sis.util.collection.T
import org.apache.sis.util.iso.DefaultNameSpace;
import org.apache.sis.util.resources.Errors;
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.filter.InvalidFilterValueException;
+// Specific to the main branch:
- import org.apache.sis.filter.Expression;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.DefaultAssociationRole;
/**
@@@ -76,9 -80,16 +78,16 @@@ final class Table extends AbstractFeatu
* except synthetic attributes like "sis:identifier". The feature may also contain associations
* inferred from foreigner keys that are not immediately apparent in the table.
*
+ * <h4>Relationship with {@code FeatureSet.getType()}</h4>
+ * When this {@code Table} has been created by inspection of the database metadata, this feature type
+ * is the value shown to users. But when this {@code Table} has been created from a query, this value
+ * is not directly shown to user because it may contain additional properties for the dependencies of
+ * operations. In the latter case, this table is hidden behind {@link FeatureIterator} and the type
+ * seen by user is provided by {@link org.apache.sis.storage.FeatureSubset#resultType}.
+ *
* @see #getType()
*/
- final FeatureType featureType;
+ final DefaultFeatureType featureType;
/**
* The SQL query to execute for fetching data, or {@code null} for querying the table identified by {@link #name}.
@@@ -193,13 -204,13 +202,13 @@@
* Creates a new table as a projection (subset of columns) of the given table.
* The columns to retain, potentially under different names, are specified in {@code projection}.
* The projection may also contain complex expressions that cannot be handled by this constructor.
- * Such expressions are stored in the {@code unhandled} map.
+ * Such expressions have their indexes stored in the {@code unhandled} set.
*
- * @param table the source table.
+ * @param source the source table.
* @param projection description of the columns to keep.
* @param reusedNames an initially empty set where to store the names of attributes that are not renamed.
- * @param unhandled an initially empty map where to add expressions that are not handled by the new table.
+ * @param unhandled where to set the bits for indexes of expressions that are not handled by the new table.
- * @throws InvalidFilterValueException if there is an error in the declaration of property values.
+ * @throws IllegalArgumentException if there is an error in the declaration of property values.
*/
@SuppressWarnings("LocalVariableHidesMemberVariable")
Table(final Table source,
@@@ -384,9 -395,11 +393,11 @@@
/**
* Returns the feature type inferred from the database structure analysis.
+ * Note that this type may not be the type shown to user if this table is
+ * created behind a {@link org.apache.sis.storage.FeatureSubset}.
*/
@Override
- public final FeatureType getType() {
+ public final DefaultFeatureType getType() {
return featureType;
}
diff --cc endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/SQLStoreTest.java
index fe55cf6f5c,62b25931c2..6b88e592f4
--- a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/SQLStoreTest.java
+++ b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/SQLStoreTest.java
@@@ -439,6 -445,30 +441,30 @@@ public final class SQLStoreTest extend
assertEquals("FeatureStream[table=“Countries”, predicates=“SQL”]", executionMode);
}
+ /**
+ * Requests a new set of features with a subset containing the "sis:identifier" property.
+ * The difficulty is that no column named "sis:column" exists. The property is a link to
+ * the "code" attribute, which is intentionally omitted in the projection (in SQL sense)
+ * in order to test the capability to follow dependencies.
+ *
+ * @param dataset the store on which to query the features.
+ * @throws DataStoreException if an error occurred during query execution.
+ */
+ private void verifyLinkInProjection(final SimpleFeatureStore dataset) throws Exception {
+ final var query = new FeatureQuery();
+ query.setProjection("sis:identifier", "native_name");
+ final FeatureSet countries = dataset.findResource("Countries").subset(query);
+ List<Object> names;
- try (Stream<Feature> features = countries.features(false)) {
++ try (Stream<AbstractFeature> features = countries.features(false)) {
+ names = features.map(f -> f.getPropertyValue("sis:identifier")).toList();
+ }
+ assertSetEquals(List.of("CAN", "FRA", "JPN"), names);
- try (Stream<Feature> features = countries.features(false)) {
++ try (Stream<AbstractFeature> features = countries.features(false)) {
+ names = features.map(f -> f.getPropertyValue("native_name")).toList();
+ }
+ assertSetEquals(List.of("Canada", "France", "日本"), names);
+ }
+
/**
* Checks that operations stacked on feature stream are well executed.
* This test focuses on mapping and peeking actions overloaded by SQL streams.
diff --cc endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureQuery.java
index 5f2d84d995,80dec5de00..7add475ead
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureQuery.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureQuery.java
@@@ -40,15 -45,20 +45,16 @@@ import org.apache.sis.util.CharSequence
import org.apache.sis.util.Emptiable;
import org.apache.sis.util.iso.Names;
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Operation;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.Filter;
-import org.opengis.filter.Expression;
-import org.opengis.filter.InvalidFilterValueException;
-import org.opengis.filter.Literal;
-import org.opengis.filter.ValueReference;
-import org.opengis.filter.SortBy;
-import org.opengis.filter.SortProperty;
+// Specific to the main branch:
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractAttribute;
++import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.filter.Filter;
+import org.apache.sis.filter.Expression;
+import org.apache.sis.pending.geoapi.filter.Literal;
+import org.apache.sis.pending.geoapi.filter.ValueReference;
+import org.apache.sis.pending.geoapi.filter.SortBy;
+import org.apache.sis.pending.geoapi.filter.SortProperty;
/**
@@@ -387,8 -395,9 +393,9 @@@ public class FeatureQuery extends Quer
*/
@SafeVarargs
@SuppressWarnings("varargs")
- public final void setSortBy(final SortProperty<Feature>... properties) {
+ final void setSortBy(final SortProperty<AbstractFeature>... properties) {
+ @SuppressWarnings("LocalVariableHidesMemberVariable")
- SortBy<Feature> sortBy = null;
+ SortBy<AbstractFeature> sortBy = null;
if (properties != null) {
sortBy = SortByComparator.create(properties);
}
@@@ -609,6 -614,25 +616,25 @@@
this.alias = alias;
}
+ /**
+ * Adds this named expression as a property into the given builder.
+ *
+ * @param builder the builder where to add the property.
+ * @return whether the property has been successfully added.
+ */
+ final boolean addTo(final FeatureProjectionBuilder builder) {
- final FeatureExpression<? super Feature, ?> fex = FeatureExpression.castOrCopy(expression);
++ final FeatureExpression<? super AbstractFeature, ?> fex = FeatureExpression.castOrCopy(expression);
+ if (fex != null) {
+ final FeatureProjectionBuilder.Item item = fex.expectedType(builder);
+ if (item != null) {
+ item.setName(alias); // Need to be invoked aven if the alias is null.
+ item.setValueGetter(expression, type == ProjectionType.STORED);
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Returns a hash code value for this column.
*
@@@ -709,6 -733,36 +735,36 @@@
return xpaths;
}
+ /**
+ * Creates the projection (in <abbr>SQL</abbr> sense) of the given feature type.
+ * If some expressions have no name, default names are computed as below:
+ *
+ * <ul>
+ * <li>If the expression is an instance of {@link ValueReference}, the name of the
+ * property referenced by the {@linkplain ValueReference#getXPath() XPath}.</li>
+ * <li>Otherwise the localized string "Unnamed #1" with increasing numbers.</li>
+ * </ul>
+ *
+ * @param sourceType the feature type to project.
+ * @param locale locale for error messages, or {@code null} for the default locale.
+ */
- final Optional<FeatureProjection> project(final FeatureType sourceType, final Locale locale) {
++ final Optional<FeatureProjection> project(final DefaultFeatureType sourceType, final Locale locale) {
+ if (projection == null) {
+ return Optional.empty();
+ }
+ final var builder = new FeatureProjectionBuilder(sourceType, locale);
+ for (int column = 0; column < projection.length; column++) {
+ final NamedExpression item = projection[column];
+ if (!item.addTo(builder)) {
+ final var name = item.expression.getFunctionName().toInternationalString();
- throw new InvalidFilterValueException(Resources.forLocale(locale)
++ throw new IllegalArgumentException(Resources.forLocale(locale)
+ .getString(Resources.Keys.InvalidExpression_2, column, name));
+ }
+ }
+ builder.setName(sourceType.getName());
+ return builder.project();
+ }
+
/**
* Applies this query on the given feature set.
* This method is invoked by the default implementation of {@link FeatureSet#subset(Query)}.
diff --cc endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureSubset.java
index 88f140538c,c4d4e792a7..9230ef5e02
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureSubset.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureSubset.java
@@@ -91,12 -91,12 +91,12 @@@ final class FeatureSubset extends Abstr
* Returns a description of properties that are common to all features in this dataset.
*/
@Override
- public synchronized FeatureType getType() throws DataStoreException {
+ public synchronized DefaultFeatureType getType() throws DataStoreException {
if (resultType == null) {
- final FeatureType type = source.getType();
+ final DefaultFeatureType type = source.getType();
try {
- projection = FeatureProjection.create(type, query.getProjection());
- resultType = (projection != null) ? projection.featureType : type;
+ projection = query.project(type, listeners.getLocale()).orElse(null);
+ resultType = (projection != null) ? projection.typeRequested : type;
} catch (IllegalArgumentException e) {
throw new DataStoreContentException(Resources.forLocale(listeners.getLocale())
.getString(Resources.Keys.CanNotDeriveTypeFromFeature_1, type.getName()), e);