This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/sis.git

commit e285c91d307caeb04c1ccfe4d401342bf45cdd3e
Merge: 7232f07008 45a58d0acf
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Fri May 29 11:09:07 2026 +0200

    Merge branch 'geoapi-3.1'. Contains:
    - Replacement of `DataStoreProvider.TIMEZONE` by a more unified way to 
handle parameters.
    - Support transitive search of metadata title in tree representation.
    - Bug fixes in SQL store.

 .../org/apache/sis/console/OperationParser.java    |   2 +-
 .../org/apache/sis/coverage/RegionOfInterest.java  |   6 +-
 .../org/apache/sis/feature/FeatureOperations.java  |   4 +-
 .../main/org/apache/sis/filter/base/Node.java      |   5 +-
 .../sis/filter/sqlmm/FunctionDescription.java      |  12 +
 .../geometry/wrapper/SpatialOperationContext.java  |   3 +-
 .../org/apache/sis/metadata/AbstractMetadata.java  |  36 +-
 .../main/org/apache/sis/metadata/CacheKey.java     |  29 +-
 .../org/apache/sis/metadata/MetadataCopier.java    |  10 +-
 .../org/apache/sis/metadata/MetadataStandard.java  | 361 +++++++++++++--------
 .../org/apache/sis/metadata/MetadataVisitor.java   |  14 +-
 .../apache/sis/metadata/ModifiableMetadata.java    |  11 +-
 .../org/apache/sis/metadata/PropertyAccessor.java  |   2 +-
 .../main/org/apache/sis/metadata/Pruner.java       |   2 +-
 .../sis/metadata/StandardImplementation.java       |   6 +-
 .../main/org/apache/sis/metadata/StateChanger.java |   2 +-
 .../org/apache/sis/metadata/TitleProperty.java     |  11 +-
 .../main/org/apache/sis/metadata/TreeNode.java     |  41 ++-
 .../org/apache/sis/metadata/TreeNodeChildren.java  |  78 +++--
 .../apache/sis/metadata/ValueExistencePolicy.java  |   1 +
 .../sis/metadata/internal/shared/Merger.java       |  17 +-
 .../metadata/internal/shared/SecondaryTrait.java   |  45 ---
 .../iso/DefaultApplicationSchemaInformation.java   |   4 +-
 .../sis/metadata/iso/DefaultMetadataScope.java     |   4 +-
 .../sis/metadata/iso/acquisition/DefaultEvent.java |   4 +-
 .../iso/acquisition/DefaultInstrument.java         |   4 +-
 .../metadata/iso/acquisition/DefaultObjective.java |   2 +-
 .../metadata/iso/acquisition/DefaultOperation.java |   4 +-
 .../sis/metadata/iso/acquisition/DefaultPlan.java  |   4 +-
 .../metadata/iso/acquisition/DefaultPlatform.java  |   4 +-
 .../iso/acquisition/DefaultPlatformPass.java       |   4 +-
 .../iso/acquisition/DefaultRequirement.java        |   2 +
 .../sis/metadata/iso/acquisition/package-info.java |   2 +-
 .../iso/citation/DefaultOnlineResource.java        |   4 +-
 .../sis/metadata/iso/citation/DefaultSeries.java   |   4 +-
 .../metadata/iso/citation/DefaultTelephone.java    |   4 +-
 .../sis/metadata/iso/citation/package-info.java    |   2 +-
 .../iso/constraint/DefaultReleasability.java       |   4 +-
 .../iso/constraint/DefaultSecurityConstraints.java |   4 +-
 .../sis/metadata/iso/constraint/package-info.java  |   2 +-
 .../DefaultFeatureCatalogueDescription.java        |   4 +-
 .../metadata/iso/distribution/DefaultDataFile.java |  16 +-
 .../metadata/iso/distribution/DefaultFormat.java   |   4 +-
 .../metadata/iso/distribution/DefaultMedium.java   |   4 +-
 .../metadata/iso/distribution/package-info.java    |   2 +-
 .../iso/extent/DefaultGeographicDescription.java   |   4 +-
 .../sis/metadata/iso/extent/package-info.java      |   2 +-
 .../iso/identification/AbstractIdentification.java |   4 +-
 .../DefaultAggregateInformation.java               |   7 +-
 .../identification/DefaultAssociatedResource.java  |   4 +-
 .../iso/identification/DefaultBrowseGraphic.java   |   4 +-
 .../identification/DefaultDataIdentification.java  |   4 +-
 .../metadata/iso/identification/package-info.java  |   2 +-
 .../sis/metadata/iso/lineage/DefaultAlgorithm.java |   4 +-
 .../sis/metadata/iso/lineage/DefaultLineage.java   |   4 +-
 .../metadata/iso/lineage/DefaultProcessing.java    |   4 +-
 .../sis/metadata/iso/lineage/package-info.java     |   2 +-
 .../org/apache/sis/metadata/iso/package-info.java  |   2 +-
 .../sis/metadata/iso/quality/AbstractElement.java  |   4 +-
 .../iso/quality/DefaultConformanceResult.java      |   4 +-
 .../iso/quality/DefaultEvaluationMethod.java       |   4 +-
 .../DefaultEvaluationReportInformation.java        |   4 +-
 .../iso/quality/DefaultSourceReference.java        |   4 +-
 .../sis/metadata/iso/quality/package-info.java     |   2 +-
 .../sis/metadata/iso/spatial/DefaultGCP.java       |   2 +-
 .../metadata/iso/spatial/DefaultGCPCollection.java |  16 +-
 .../metadata/simple/SimpleIdentifiedObject.java    |  23 +-
 .../apache/sis/metadata/simple/SimpleMetadata.java | 119 ++++++-
 .../apache/sis/metadata/sql/MetadataSource.java    |  14 +-
 .../apache/sis/metadata/sql/MetadataWriter.java    |  30 +-
 .../org/apache/sis/temporal/DefaultInstant.java    |  13 +
 .../org/apache/sis/temporal/DefaultPeriod.java     |  13 +
 .../main/org/apache/sis/temporal/TemporalDate.java |  13 +
 .../sis/xml/bind/metadata/replace/Parameter.java   |  15 +-
 .../metadata/replace/ReferenceSystemMetadata.java  |  13 +
 .../apache/sis/metadata/MetadataStandardTest.java  |   8 +-
 .../sis/metadata/ModifiableMetadataTest.java       |   1 +
 .../sis/metadata/PropertyConsistencyCheck.java     |  16 +-
 .../apache/sis/metadata/TreeNodeChildrenTest.java  |   5 +-
 .../sis/openoffice/ReferencingFunctions.java       |  10 +-
 .../gazetteer/ReferencingByIdentifiers.java        |   1 +
 .../sis/referencing/gazetteer/package-info.java    |   2 +-
 .../sis/coordinate/DefaultCoordinateMetadata.java  |   3 +-
 .../main/org/apache/sis/io/wkt/WKTDictionary.java  |   8 +-
 .../sis/parameter/AbstractParameterDescriptor.java |   8 +-
 .../sis/parameter/DefaultParameterDescriptor.java  |  18 +-
 .../parameter/DefaultParameterDescriptorGroup.java |  15 +-
 .../sis/parameter/DefaultParameterValue.java       |  15 +-
 .../apache/sis/parameter/ParameterizedType.java    |  49 +++
 .../main/org/apache/sis/parameter/Parameters.java  |  17 +-
 .../org/apache/sis/parameter/package-info.java     |   2 +-
 .../sis/referencing/AbstractIdentifiedObject.java  |  65 ++--
 .../sis/referencing/AbstractReferenceSystem.java   |   8 +-
 .../main/org/apache/sis/referencing/Builder.java   |   6 +-
 .../apache/sis/referencing/GeodeticCalculator.java |   5 +-
 .../apache/sis/referencing/crs/AbstractCRS.java    |   8 +-
 .../sis/referencing/crs/AbstractDerivedCRS.java    |   2 +-
 .../sis/referencing/crs/AbstractSingleCRS.java     |   5 +-
 .../sis/referencing/crs/DefaultCompoundCRS.java    |  15 +-
 .../sis/referencing/crs/DefaultDerivedCRS.java     |  29 +-
 .../sis/referencing/crs/DefaultEngineeringCRS.java |  15 +-
 .../sis/referencing/crs/DefaultGeocentricCRS.java  |  10 +-
 .../sis/referencing/crs/DefaultGeodeticCRS.java    |  10 +-
 .../sis/referencing/crs/DefaultGeographicCRS.java  |  13 +-
 .../sis/referencing/crs/DefaultProjectedCRS.java   |  15 +-
 .../sis/referencing/crs/DefaultTemporalCRS.java    |  15 +-
 .../sis/referencing/crs/DefaultVerticalCRS.java    |  15 +-
 .../apache/sis/referencing/crs/package-info.java   |   2 +-
 .../org/apache/sis/referencing/cs/AbstractCS.java  |  11 +-
 .../apache/sis/referencing/cs/DefaultAffineCS.java |   7 +-
 .../sis/referencing/cs/DefaultCartesianCS.java     |  14 +-
 .../cs/DefaultCoordinateSystemAxis.java            |  15 +-
 .../sis/referencing/cs/DefaultCylindricalCS.java   |  14 +-
 .../sis/referencing/cs/DefaultEllipsoidalCS.java   |  14 +-
 .../apache/sis/referencing/cs/DefaultLinearCS.java |  14 +-
 .../apache/sis/referencing/cs/DefaultPolarCS.java  |  14 +-
 .../sis/referencing/cs/DefaultSphericalCS.java     |  14 +-
 .../apache/sis/referencing/cs/DefaultTimeCS.java   |  12 +-
 .../sis/referencing/cs/DefaultVerticalCS.java      |  14 +-
 .../org/apache/sis/referencing/cs/Normalizer.java  |   2 +-
 .../sis/referencing/datum/AbstractDatum.java       |   7 +-
 .../referencing/datum/DefaultDatumEnsemble.java    |  24 +-
 .../sis/referencing/datum/DefaultEllipsoid.java    |  15 +-
 .../referencing/datum/DefaultEngineeringDatum.java |  15 +-
 .../referencing/datum/DefaultGeodeticDatum.java    |  15 +-
 .../referencing/datum/DefaultParametricDatum.java  |   2 +-
 .../referencing/datum/DefaultPrimeMeridian.java    |  15 +-
 .../referencing/datum/DefaultTemporalDatum.java    |  15 +-
 .../referencing/datum/DefaultVerticalDatum.java    |  15 +-
 .../factory/GeodeticAuthorityFactory.java          |   3 +-
 .../referencing/factory/GeodeticObjectFactory.java |   2 +-
 .../referencing/internal/ParameterizedType.java    | 118 +++++++
 .../internal/shared/AffineTransform2D.java         |  11 +
 .../internal/shared/ReferencingUtilities.java      |  51 +--
 .../sis/referencing/legacy/DefaultImageCRS.java    |   3 +-
 .../sis/referencing/legacy/DefaultImageDatum.java  |  12 +-
 .../referencing/legacy/DefaultUserDefinedCS.java   |  11 +-
 .../operation/AbstractCoordinateOperation.java     |   6 +-
 .../apache/sis/referencing/operation/CRSPair.java  |   3 +-
 .../operation/CoordinateOperationRegistry.java     |   2 +-
 .../operation/DefaultConcatenatedOperation.java    |  12 +-
 .../referencing/operation/DefaultConversion.java   |  10 +-
 .../DefaultCoordinateOperationFactory.java         |   6 +-
 .../operation/DefaultOperationMethod.java          |  15 +-
 .../operation/DefaultPassThroughOperation.java     |  15 +-
 .../referencing/operation/DefaultProjection.java   |   2 +-
 .../operation/DefaultTransformation.java           |  15 +-
 .../referencing/operation/matrix/MatrixSIS.java    |  20 +-
 .../referencing/operation/matrix/package-info.java |   2 +-
 .../operation/transform/AbstractMathTransform.java |  20 +-
 .../operation/transform/package-info.java          |   2 +-
 .../DefaultParameterDescriptorGroupTest.java       |  20 +-
 .../parameter/DefaultParameterDescriptorTest.java  | 119 +++----
 .../sis/parameter/DefaultParameterValueTest.java   |  46 +--
 .../datum/DefaultDatumEnsembleTest.java            |  96 ++++++
 .../datum/DefaultGeodeticDatumTest.java            |   6 +-
 .../sis/referencing/datum/HardCodedDatum.java      |   2 +-
 .../apache/sis/storage/landsat/LandsatStore.java   |   4 +-
 .../sis/storage/landsat/LandsatStoreProvider.java  |   4 +-
 .../apache/sis/storage/geotiff/GeoTiffStore.java   |   6 +-
 .../sis/storage/geotiff/GeoTiffStoreProvider.java  |  13 +-
 .../sis/storage/geotiff/writer/GeoEncoder.java     |   6 +-
 .../org/apache/sis/storage/netcdf/NetcdfStore.java |   4 +-
 .../sis/storage/netcdf/NetcdfStoreProvider.java    |  10 +-
 .../sis/storage/netcdf/classic/VariableInfo.java   |   4 +-
 .../org/apache/sis/storage/sql/feature/Column.java |   3 +-
 .../apache/sis/storage/sql/feature/Database.java   |  21 ++
 .../sis/storage/sql/feature/InfoStatements.java    |   8 +-
 .../apache/sis/storage/sql/postgis/Postgres.java   |  23 ++
 .../main/org/apache/sis/storage/gpx/Metadata.java  |   6 +-
 .../main/org/apache/sis/storage/gpx/Store.java     |   2 +-
 .../org/apache/sis/storage/gpx/StoreProvider.java  |  16 +-
 .../storage/xml/stream/StaxDataStoreProvider.java  |   8 +-
 .../main/org/apache/sis/storage/DataStore.java     |  27 +-
 .../org/apache/sis/storage/DataStoreProvider.java  |  32 +-
 .../main/org/apache/sis/storage/OptionKey.java     |  99 +++++-
 .../org/apache/sis/storage/StorageConnector.java   |   1 +
 .../org/apache/sis/storage/base/PRJDataStore.java  | 100 ++----
 .../apache/sis/storage/base/StoreUtilities.java    | 159 ++-------
 .../org/apache/sis/storage/base/URIDataStore.java  |  96 ++++--
 .../sis/storage/base/URIDataStoreOption.java       | 336 +++++++++++++++++++
 .../sis/storage/base/URIDataStoreProvider.java     | 178 ++++------
 .../main/org/apache/sis/storage/csv/Store.java     |  27 +-
 .../org/apache/sis/storage/csv/StoreProvider.java  |  67 +---
 .../apache/sis/storage/esri/AsciiGridStore.java    |   7 +-
 .../sis/storage/esri/AsciiGridStoreProvider.java   |   7 +-
 .../org/apache/sis/storage/esri/RasterStore.java   |   6 +-
 .../apache/sis/storage/esri/RawRasterStore.java    |   2 +-
 .../sis/storage/esri/RawRasterStoreProvider.java   |   7 +-
 .../main/org/apache/sis/storage/folder/Store.java  |  42 +--
 .../apache/sis/storage/folder/StoreProvider.java   |  93 ++----
 .../apache/sis/storage/folder/WritableStore.java   |  10 +-
 .../sis/storage/image/WorldFileStoreProvider.java  |   9 +-
 .../org/apache/sis/storage/internal/Resources.java |   7 +-
 .../sis/storage/internal/Resources.properties      |   3 +-
 .../sis/storage/internal/Resources_fr.properties   |   3 +-
 .../main/org/apache/sis/storage/wkt/Store.java     |  10 +-
 .../org/apache/sis/storage/wkt/StoreFormat.java    |   2 +-
 .../org/apache/sis/storage/wkt/StoreProvider.java  |  63 ++--
 .../apache/sis/storage/xml/AbstractProvider.java   |  13 +-
 .../org/apache/sis/storage/xml/StoreProvider.java  |   2 +-
 .../sis/storage/base/StoreUtilitiesTest.java       |  15 +-
 ...tiesTest.java => URIDataStoreProviderTest.java} |  20 +-
 .../test/org/apache/sis/storage/csv/StoreTest.java |   3 +-
 .../org/apache/sis/storage/folder/StoreTest.java   |   2 +-
 .../apache/sis/storage/wkt/StoreProviderTest.java  |   6 +-
 .../org/apache/sis/measure/AbstractConverter.java  |  21 +-
 .../main/org/apache/sis/measure/AbstractUnit.java  |  25 +-
 .../apache/sis/measure/ConcatenatedConverter.java  |   8 +-
 .../org/apache/sis/measure/LinearConverter.java    |   8 +-
 .../main/org/apache/sis/measure/package-info.java  |  10 +-
 .../main/org/apache/sis/util/Classes.java          | 217 ++++++++-----
 .../main/org/apache/sis/util/ComparisonMode.java   |   3 +
 .../org/apache/sis/util/LenientComparable.java     |  39 ++-
 .../sis/util/resources/IndexedResourceBundle.java  |   3 +
 .../test/org/apache/sis/util/ClassesTest.java      |  10 +
 .../test/org/apache/sis/util/UtilitiesTest.java    |   7 +
 .../storage/coveragejson/CoverageJsonStore.java    |   4 +-
 .../coveragejson/CoverageJsonStoreProvider.java    |   5 +-
 .../apache/sis/storage/geoheif/GeoHeifStore.java   |   4 +-
 .../sis/storage/geoheif/GeoHeifStoreProvider.java  |   9 +-
 .../sis/storage/geopackage/GpkgStoreProvider.java  |   7 +-
 .../main/org/apache/sis/storage/gsf/GSFStore.java  |   6 +-
 .../apache/sis/storage/gsf/GSFStoreProvider.java   |  25 +-
 .../sis/storage/shapefile/ShapefileProvider.java   |  45 +--
 .../sis/storage/shapefile/ShapefileStore.java      |  72 ++--
 .../sis/storage/shapefile/ShapefileStoreTest.java  |  27 +-
 .../org/apache/sis/storage/shapefile/Snippets.java |   7 +-
 .../org/apache/sis/gui/dataset/PathAction.java     |  43 ++-
 .../org/apache/sis/gui/dataset/ResourceCell.java   |   3 +-
 .../apache/sis/gui/referencing/FilterByDatum.java  |   3 +-
 .../org/apache/sis/storage/gdal/GDALStore.java     |   4 +-
 .../apache/sis/storage/gdal/GDALStoreProvider.java |   7 +-
 233 files changed, 2828 insertions(+), 1830 deletions(-)

diff --cc 
endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureOperations.java
index 1924b1603b,74862f63c9..6062d2dd48
--- 
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
@@@ -362,11 -356,11 +362,11 @@@ public final class FeatureOperations 
       * of a previous call to {@link #expression expression(…)}, then invoking 
this method is equivalent
       * to invoking {@code expression(…)} again with the same arguments except 
for {@code expression}.
       *
-      * @param  operation   the operation to evaluate in a different way.
+      * @param  property    the operation to evaluate in a different way.
       * @param  expression  the new expression to use for evaluating the 
operation.
       * @return the new operation. May be the given operation if the 
expression is the same.
 -     * @throws IllegalArgumentException if the {@linkplain 
Operation#getResult() result type}
 -     *         of the given operation is not an {@link AttributeType}.
 +     * @throws IllegalArgumentException if the {@linkplain 
AbstractOperation#getResult() result type}
 +     *         of the given operation is not an {@code AttributeType}.
       *
       * @since 1.6
       */
diff --cc 
endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/FunctionDescription.java
index 3109918e4a,b49ffea275..15cba52a1d
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/FunctionDescription.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/FunctionDescription.java
@@@ -247,12 -251,18 +247,24 @@@ final class FunctionDescription extend
              return 1;
          }
  
 +        @Override public Set<T>        getValidValues()  {return null;}
 +        @Override public Comparable<T> getMinimumValue() {return null;}
 +        @Override public Comparable<T> getMaximumValue() {return null;}
 +        @Override public T             getDefaultValue() {return null;}
 +        @Override public Unit<?>       getUnit()         {return null;}
 +
+         /**
+          * Returns the standard interface expected by {@code equals(…)} 
methods.
+          * Note that {@code equals(…)} actually require a stricter class,
+          * but we declare only the public interface here.
+          *
+          * @return {@code ParameterDescriptor.class}.
+          */
+         @Override
+         public Type getStandardType() {
+             return ParameterDescriptor.class;
+         }
+ 
          /**
           * Tests whether the given object is equal to this argument 
description.
           *
diff --cc 
endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/DefaultMetadataScope.java
index 121d6a7074,b9a3e239fa..e01db826cf
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/DefaultMetadataScope.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/DefaultMetadataScope.java
@@@ -21,13 -21,11 +21,14 @@@ import jakarta.xml.bind.annotation.XmlE
  import jakarta.xml.bind.annotation.XmlRootElement;
  import org.opengis.util.InternationalString;
  import org.opengis.metadata.maintenance.ScopeCode;
+ import org.apache.sis.metadata.TitleProperty;
  import org.apache.sis.util.iso.Types;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.metadata.MetadataScope;
 +// Specific to the main branch:
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.OPTIONAL;
 +import static org.opengis.annotation.Obligation.MANDATORY;
 +import static org.opengis.annotation.Specification.ISO_19115;
  
  
  /**
diff --cc 
endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultOnlineResource.java
index c19fd68fa6,abb64fecb5..5ff4ca2e90
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultOnlineResource.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultOnlineResource.java
@@@ -26,13 -26,9 +26,14 @@@ import org.opengis.metadata.citation.On
  import org.opengis.metadata.citation.OnlineResource;
  import org.apache.sis.xml.bind.gco.StringAdapter;
  import org.apache.sis.xml.bind.gco.URIAdapter;
+ import org.apache.sis.metadata.TitleProperty;
  import org.apache.sis.metadata.iso.ISOMetadata;
  
 +// Specific to the main branch:
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.OPTIONAL;
 +import static org.opengis.annotation.Specification.ISO_19115;
 +
  
  /**
   * Information about on-line sources from which the dataset, specification, or
diff --cc 
endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultTelephone.java
index 6c5b2accb6,fb1d58421e..8a8db9b788
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultTelephone.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultTelephone.java
@@@ -30,16 -30,11 +30,17 @@@ import org.apache.sis.xml.bind.FilterBy
  import org.apache.sis.xml.internal.shared.LegacyNamespaces;
  import org.apache.sis.xml.bind.gco.StringAdapter;
  import org.apache.sis.xml.bind.metadata.code.CI_TelephoneTypeCode;
+ import org.apache.sis.metadata.TitleProperty;
  import org.apache.sis.metadata.internal.Dependencies;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.metadata.citation.TelephoneType;
 +// Specific to the main branch:
 +import org.opengis.util.CodeList;
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.OPTIONAL;
 +import static org.opengis.annotation.Obligation.MANDATORY;
 +import static org.opengis.annotation.Specification.ISO_19115;
 +import org.apache.sis.pending.geoapi.evolution.InterimType;
 +import org.apache.sis.pending.geoapi.evolution.UnsupportedCodeList;
  
  
  /**
diff --cc 
endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/constraint/DefaultReleasability.java
index 5c55f902c6,490e49dee5..b2b452ca82
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/constraint/DefaultReleasability.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/constraint/DefaultReleasability.java
@@@ -22,13 -22,12 +22,14 @@@ import jakarta.xml.bind.annotation.XmlE
  import jakarta.xml.bind.annotation.XmlRootElement;
  import org.opengis.util.InternationalString;
  import org.opengis.metadata.constraint.Restriction;
+ import org.apache.sis.metadata.TitleProperty;
  import org.apache.sis.metadata.iso.ISOMetadata;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.metadata.constraint.Releasability;
 -import org.opengis.metadata.citation.Responsibility;
 +// Specific to the main branch:
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.OPTIONAL;
 +import static org.opengis.annotation.Specification.ISO_19115;
 +import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
  
  
  /**
diff --cc 
endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultFeatureCatalogueDescription.java
index a6fe7e5e2e,c04b1987b7..573bf4c6e5
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultFeatureCatalogueDescription.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/DefaultFeatureCatalogueDescription.java
@@@ -198,9 -190,9 +198,9 @@@ public class DefaultFeatureCatalogueDes
       *
       * @since 1.0
       */
 -    @Override
 +    @UML(identifier="locale", obligation=CONDITIONAL, specification=ISO_19115)
      // @XmlElement at the end of this class.
-     public Map<Locale,Charset> getLocalesAndCharsets() {
+     public Map<Locale, Charset> getLocalesAndCharsets() {
          return locales = nonNullMap(locales, Locale.class);
      }
  
diff --cc 
endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultDataFile.java
index 089730d5e2,8f186cab2e..bde2c10045
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultDataFile.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultDataFile.java
@@@ -34,6 -35,6 +35,11 @@@ import org.apache.sis.xml.internal.shar
  // Specific to the main and geoapi-3.1 branches:
  import org.opengis.util.LocalName;
  
++// Specific to the main branch:
++import org.opengis.annotation.UML;
++import static org.opengis.annotation.Obligation.MANDATORY;
++import static org.opengis.annotation.Specification.ISO_19115;
++
  
  /**
   * Description of a transfer data file.
@@@ -170,6 -169,7 +177,7 @@@ public class DefaultDataFile extends IS
       * @see 
org.apache.sis.metadata.iso.identification.DefaultBrowseGraphic#getFileName()
       * @since 1.0
       */
 -    @Override
++    @UML(identifier="fileName", obligation=MANDATORY, 
specification=ISO_19115)  // Actually ISO_19115_3
      @XmlElement(name = "fileName", required = true)
      public URI getFileName() {
          return fileName;
diff --cc 
endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultFormat.java
index 61961893cb,9b13f58861..278e89f737
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultFormat.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultFormat.java
@@@ -35,14 -35,9 +35,15 @@@ import org.apache.sis.xml.internal.shar
  import org.apache.sis.xml.bind.metadata.MD_Medium;
  import org.apache.sis.xml.bind.metadata.CI_Citation;
  import org.apache.sis.util.collection.Containers;
+ import org.apache.sis.metadata.TitleProperty;
  import org.apache.sis.metadata.iso.citation.DefaultCitation;
  
 +// Specific to the main branch:
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.OPTIONAL;
 +import static org.opengis.annotation.Obligation.MANDATORY;
 +import static org.opengis.annotation.Specification.ISO_19115;
 +
  
  /**
   * Description of the computer language construct that specifies the 
representation
diff --cc 
endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultAssociatedResource.java
index 1a2417928f,047899f7a7..d9792602cf
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultAssociatedResource.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultAssociatedResource.java
@@@ -26,15 -26,11 +26,16 @@@ import org.opengis.metadata.identificat
  import org.apache.sis.xml.bind.metadata.CI_Citation;
  import org.apache.sis.xml.bind.metadata.code.DS_AssociationTypeCode;
  import org.apache.sis.xml.bind.metadata.code.DS_InitiativeTypeCode;
+ import org.apache.sis.metadata.TitleProperty;
  import org.apache.sis.metadata.iso.ISOMetadata;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.metadata.identification.AssociatedResource;
 +// Specific to the main branch:
 +import org.opengis.metadata.identification.AggregateInformation;
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.OPTIONAL;
 +import static org.opengis.annotation.Obligation.MANDATORY;
 +import static org.opengis.annotation.Obligation.CONDITIONAL;
 +import static org.opengis.annotation.Specification.ISO_19115;
  
  
  /**
diff --cc 
endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultDataIdentification.java
index 0ef0564a6e,19bdfe69ae..4658268997
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultDataIdentification.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultDataIdentification.java
@@@ -205,9 -195,9 +205,9 @@@ public class DefaultDataIdentification 
       *
       * @since 1.0
       */
 -    @Override
 +    @UML(identifier="defaultLocale+otherLocale", obligation=CONDITIONAL, 
specification=ISO_19115)
      // @XmlElement at the end of this class.
-     public Map<Locale,Charset> getLocalesAndCharsets() {
+     public Map<Locale, Charset> getLocalesAndCharsets() {
          return locales = nonNullMap(locales, Locale.class);
      }
  
diff --cc 
endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultEvaluationReportInformation.java
index e53b528e0c,f8a278fea3..270a3c7ce2
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultEvaluationReportInformation.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultEvaluationReportInformation.java
@@@ -21,11 -21,10 +21,12 @@@ import jakarta.xml.bind.annotation.XmlE
  import jakarta.xml.bind.annotation.XmlRootElement;
  import org.opengis.util.InternationalString;
  import org.opengis.metadata.citation.Citation;
+ import org.apache.sis.metadata.TitleProperty;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.metadata.quality.StandaloneQualityReportInformation;
 +// Specific to the main branch:
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.MANDATORY;
 +import static org.opengis.annotation.Specification.UNSPECIFIED;
  
  
  /**
diff --cc 
endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultSourceReference.java
index 6a69eb7f34,a076cec35b..4093545401
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultSourceReference.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultSourceReference.java
@@@ -20,12 -20,11 +20,13 @@@ import jakarta.xml.bind.annotation.XmlT
  import jakarta.xml.bind.annotation.XmlElement;
  import jakarta.xml.bind.annotation.XmlRootElement;
  import org.opengis.metadata.citation.Citation;
+ import org.apache.sis.metadata.TitleProperty;
  import org.apache.sis.xml.Namespaces;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.metadata.quality.SourceReference;
 +// Specific to the main branch:
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.MANDATORY;
 +import static org.opengis.annotation.Specification.UNSPECIFIED;
  
  
  /**
@@@ -46,13 -45,13 +47,14 @@@
   *
   * @author  Alexis Gaillard (Geomatys)
   * @author  Martin Desruisseaux (Geomatys)
-  * @version 1.4
+  * @version 1.7
   * @since   1.3
   */
+ @TitleProperty(name = "citation")
  @XmlType(name = "DQM_SourceReference_Type", namespace = Namespaces.DQM)
  @XmlRootElement(name = "DQM_SourceReference", namespace = Namespaces.DQM)
 -public class DefaultSourceReference extends ISOMetadata implements 
SourceReference {
 +@UML(identifier="DQM_SourceReference", specification=UNSPECIFIED)
 +public class DefaultSourceReference extends ISOMetadata {
      /**
       * Serial number for inter-operability with different versions.
       */
diff --cc 
endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleIdentifiedObject.java
index 8b7b6722eb,902b2389f8..cd2432a33b
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleIdentifiedObject.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleIdentifiedObject.java
@@@ -155,21 -125,33 +156,35 @@@ public class SimpleIdentifiedObject imp
      }
  
      /**
 -     * Returns a narrative explanation of the role of this object.
 -     * The default implementation returns {@link Identifier#getDescription()}.
 +     * Method required by the {@link IdentifiedObject} interface.
 +     * Current implementation returns {@code null}.
 +     *
 +     * <p>If a future version allows this method to returns a non-null value,
 +     * revisit {@link #equals(Object, ComparisonMode)}.</p>
       *
 -     * @return a narrative explanation of the role of this object.
 +     * @return the remarks, or {@code null} if none.
       */
 -    public Optional<InternationalString> getDescription() {
 -        @SuppressWarnings("LocalVariableHidesMemberVariable")
 -        final Identifier name = this.name;
 -        return Optional.ofNullable((name != null) ? name.getDescription() : 
null);
 +    @Override
 +    public final InternationalString getRemarks() {
 +        return null;
      }
  
+     /**
+      * Returns the standard interface that defines the contract of this class.
+      * This is the base type required by all {@code equals(…)} methods
+      * for returning a potentially {@code true} value.
+      *
+      * @return {@code IdentifiedObject.class} or a subtype.
+      */
+     @Override
+     public Type getStandardType() {
+         return IdentifiedObject.class;
+     }
+ 
      /**
       * Returns a hash code value for this object.
+      *
+      * @return an arbitrary hash code value.
       */
      @Override
      public int hashCode() {
@@@ -223,19 -205,10 +238,21 @@@
          return false;
      }
  
 +    /**
 +     * Throws an exception in all cases, since this object can't be formatted 
in a valid WKT.
 +     *
 +     * @return the Well Known Text.
 +     * @throws UnsupportedOperationException always thrown.
 +     */
 +    @Override
 +    public String toWKT() throws UnsupportedOperationException {
 +        throw new UnsupportedOperationException();
 +    }
 +
      /**
-      * Returns a pseudo-WKT representation for debugging purpose.
+      * Returns a pseudo-<abbr>WKT</abbr> representation for debugging 
purposes.
+      *
+      * @return a string representation for debugging purposes.
       */
      @Override
      public String toString() {
diff --cc 
endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleMetadata.java
index 306aaa2440,0c0d38f7d5..e619a9b3ad
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleMetadata.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleMetadata.java
@@@ -86,83 -74,41 +86,101 @@@ public class SimpleMetadata implements 
      protected SimpleMetadata() {
      }
  
 +    /**
 +     * Unique identifier for this metadata record.
++     *
++     * @return {@code null}.
 +     */
 +    @Override
 +    public String getFileIdentifier() {
 +        return null;
 +    }
 +
      /**
       * Language(s) used for documenting metadata.
       * Also the language(s) used within the data.
+      *
+      * @return empty map.
       */
      @Override
 -    public Map<Locale, Charset> getLocalesAndCharsets() {
 -        return Collections.emptyMap();
 +    public Collection<Locale> getLanguages() {
 +        return Collections.emptySet();                  // We use 'Set' 
because we handle 'Locale' like a CodeList.
 +    }
 +
 +    /**
 +     * Language(s) used for documenting metadata.
 +     * Also the language(s) used within the data.
++     *
++     * @return empty map.
 +     */
 +    @Override
 +    public Locale getLanguage() {
 +        return null;
 +    }
 +
 +    /**
 +     * Language(s) used for documenting metadata.
 +     * Also the language(s) used within the data.
++     *
++     * @return empty collection.
 +     */
 +    @Override
 +    public Collection<Locale> getLocales() {
 +        return Collections.emptySet();                  // We use 'Set' 
because we handle 'Locale' like a CodeList.
      }
  
      /**
 -     * The scope or type of resource for which metadata is provided.
 -     * This method returns {@code this} for allowing call to {@link 
#getResourceScope()}.
 +     * The character coding standard used for the metadata set.
 +     * Also the character coding standard(s) used for the dataset.
+      *
+      * @return empty collection.
 +     */
 +    @Override
 +    public Collection<CharacterSet> getCharacterSets() {
 +        return Collections.emptySet();                  // We use 'Set' 
because we handle 'Charset' like a CodeList.
 +    }
 +
 +    /**
 +     * The character coding standard used for the metadata set.
 +     * Also the character coding standard(s) used for the dataset.
+      *
 -     * @see #getResourceScope()
 -     * @see #getName()
++     * @return {@code null}.
       */
      @Override
 -    public Collection<MetadataScope> getMetadataScopes() {
 -        return Collections.singleton(this);
 +    public CharacterSet getCharacterSet() {
 +        return null;
      }
  
      /**
 -     * Code for the metadata scope, fixed to {@link ScopeCode#DATASET} by 
default. This is part of the information
 -     * provided by {@link #getMetadataScopes()}. The {@code DATASET} default 
value is consistent with the fact that
 +     * Identification of the parent metadata record.
++     *
++     * @return {@code null}.
 +     */
 +    @Override
 +    public String getParentIdentifier() {
 +        return null;
 +    }
 +
 +    /**
 +     * Code for the metadata scope, fixed to {@link ScopeCode#DATASET} by 
default.
 +     * The {@code DATASET} default value is consistent with the fact that
       * {@code SimpleMetadata} implements {@link DataIdentification}.
+      *
+      * @return {@code DATASET}.
       */
      @Override
 -    public ScopeCode getResourceScope() {
 -        return ScopeCode.DATASET;
 +    public Collection<ScopeCode> getHierarchyLevels() {
 +        return Collections.singleton(ScopeCode.DATASET);
 +    }
 +
 +    /**
 +     * Description of the metadata scope.
++     *
++     * @return empty collection.
 +     */
 +    @Override
 +    public Collection<String> getHierarchyLevelNames() {
 +        return Collections.emptySet();
      }
  
      /**
@@@ -175,58 -123,11 +195,72 @@@
  
      /**
       * Date(s) associated with the metadata.
+      *
+      * @return empty collection.
       */
      @Override
 -    public Collection<CitationDate> getDateInfo() {
 +    public Date getDateStamp() {
 +        return null;
 +    }
 +
 +    /**
 +     * Citation(s) for the standard(s) to which the metadata conform.
++     *
++     * @return {@code null}.
 +     */
 +    @Override
 +    public String getMetadataStandardName() {
 +        return null;
 +    }
 +
 +    /**
 +     * As of ISO 19115:2014, replaced by {@code getMetadataStandards()}
 +     * followed by {@link Citation#getEdition()}.
++     *
++     * @return {@code null}.
 +     */
 +    @Override
 +    public String getMetadataStandardVersion() {
 +        return null;
 +    }
 +
 +    /**
 +     * Online location(s) where the metadata is available.
++     *
++     * @return {@code null}.
 +     */
 +    @Override
 +    public String getDataSetUri() {
 +        return null;
 +    }
 +
 +    /**
 +     * Digital representation of spatial information in the dataset.
++     *
++     * @return empty collection.
 +     */
 +    @Override
 +    public Collection<SpatialRepresentation> getSpatialRepresentationInfo() {
 +        return Collections.emptyList();
 +    }
 +
 +    /**
 +     * Description of the spatial and temporal reference systems used in the 
dataset.
++     *
++     * @return empty collection.
 +     */
 +    @Override
 +    public Collection<ReferenceSystem> getReferenceSystemInfo() {
 +        return Collections.emptyList();
 +    }
 +
 +    /**
 +     * Information describing metadata extensions.
++     *
++     * @return empty collection.
 +     */
 +    @Override
 +    public Collection<MetadataExtensionInformation> 
getMetadataExtensionInfo() {
          return Collections.emptyList();
      }
  
@@@ -250,70 -153,6 +285,86 @@@
          return Collections.singleton(this);
      }
  
 +    /**
 +     * Information about the feature and coverage characteristics.
++     *
++     * @return empty collection.
 +     */
 +    @Override
 +    public Collection<ContentInformation> getContentInfo() {
 +        return Collections.emptyList();
 +    }
 +
 +    /**
 +     * Information about the distributor of and options for obtaining the 
resource(s).
++     *
++     * @return {@code null}.
 +     */
 +    @Override
 +    public Distribution getDistributionInfo() {
 +        return null;
 +    }
 +
 +    /**
 +     * Overall assessment of quality of a resource(s).
++     *
++     * @return empty collection.
 +     */
 +    @Override
 +    public Collection<DataQuality> getDataQualityInfo() {
 +        return Collections.emptyList();
 +    }
 +
 +    /**
 +     * Information about the catalogue of rules defined for the portrayal of 
a resource(s).
++     *
++     * @return empty collection.
 +     */
 +    @Override
 +    public Collection<PortrayalCatalogueReference> 
getPortrayalCatalogueInfo() {
 +        return Collections.emptyList();
 +    }
 +
 +    /**
 +     * Restrictions on the access and use of metadata.
++     *
++     * @return empty collection.
 +     */
 +    @Override
 +    public Collection<Constraints> getMetadataConstraints() {
 +        return Collections.emptyList();
 +    }
 +
 +    /**
 +     * Information about the conceptual schema of a dataset.
++     *
++     * @return empty collection.
 +     */
 +    @Override
 +    public Collection<ApplicationSchemaInformation> 
getApplicationSchemaInfo() {
 +        return Collections.emptyList();
 +    }
 +
 +    /**
 +     * Information about the acquisition of the data.
++     *
++     * @return empty collection.
 +     */
 +    @Override
 +    public Collection<AcquisitionInformation> getAcquisitionInformation() {
 +        return Collections.emptyList();
 +    }
 +
 +    /**
 +     * Information about the frequency of metadata updates, and the scope of 
those updates.
++     *
++     * @return {@code null}.
 +     */
 +    @Override
 +    public MaintenanceInformation getMetadataMaintenance() {
 +        return null;
 +    }
 +
  
      /* 
-------------------------------------------------------------------------------------------------
       * Implementation of the DataIdentification object returned by 
Metadata.getIdentificationInfo().
@@@ -332,48 -173,14 +385,58 @@@
      /**
       * Brief narrative summary of the resource.
       * This is part of the information returned by {@link 
#getIdentificationInfo()}.
+      *
 -     * @return null.
++     * @return {@code null}.
       */
      @Override
      public InternationalString getAbstract() {
          return null;
      }
  
 +    /**
 +     * Summary of the intentions with which the resource was developed.
 +     * This is part of the information returned by {@link 
#getIdentificationInfo()}.
++     *
++     * @return {@code null}.
 +     */
 +    @Override
 +    public InternationalString getPurpose() {
 +        return null;
 +    }
 +
 +    /**
 +     * Recognition of those who contributed to the resource.
 +     * This is part of the information returned by {@link 
#getIdentificationInfo()}.
++     *
++     * @return empty collection.
 +     */
 +    @Override
 +    public Collection<String> getCredits() {
 +        return Collections.emptyList();
 +    }
 +
 +    /**
 +     * Status of the resource.
 +     * This is part of the information returned by {@link 
#getIdentificationInfo()}.
++     *
++     * @return empty collection.
 +     */
 +    @Override
 +    public Collection<Progress> getStatus() {
 +        return Collections.emptySet();              // We use 'Set' because 
'Progress' is a CodeList.
 +    }
 +
 +    /**
 +     * Identification of, and means of communication with, person(s) and 
organisations associated with the resource(s).
 +     * This is part of the information returned by {@link 
#getIdentificationInfo()}.
++     *
++     * @return empty collection.
 +     */
 +    @Override
 +    public Collection<ResponsibleParty> getPointOfContacts() {
 +        return Collections.emptyList();
 +    }
 +
      /**
       * Methods used to spatially represent geographic information.
       * This is part of the information returned by {@link 
#getIdentificationInfo()}.
@@@ -385,15 -194,6 +450,17 @@@
          return Collections.singleton(SpatialRepresentationType.VECTOR);
      }
  
 +    /**
 +     * Factor which provides a general understanding of the density of 
spatial data in the resource.
 +     * This is part of the information returned by {@link 
#getIdentificationInfo()}.
++     *
++     * @return empty collection.
 +     */
 +    @Override
 +    public Collection<Resolution> getSpatialResolutions() {
 +        return Collections.emptyList();
 +    }
 +
      /**
       * Main theme(s) of the resource.
       * This is part of the information returned by {@link 
#getIdentificationInfo()}.
@@@ -414,89 -218,6 +485,107 @@@
          return Collections.emptyList();
      }
  
 +    /**
 +     * Information about the frequency of resource updates, and the scope of 
those updates.
 +     * This is part of the information returned by {@link 
#getIdentificationInfo()}.
++     *
++     * @return empty collection.
 +     */
 +    @Override
 +    public Collection<MaintenanceInformation> getResourceMaintenances() {
 +        return Collections.emptyList();
 +    }
 +
 +    /**
 +     * Graphic that illustrates the resource(s) (should include a legend for 
the graphic).
 +     * This is part of the information returned by {@link 
#getIdentificationInfo()}.
++     *
++     * @return empty collection.
 +     */
 +    @Override
 +    public Collection<BrowseGraphic> getGraphicOverviews() {
 +        return Collections.emptyList();
 +    }
 +
 +    /**
 +     * Description of the format of the resource(s).
 +     * This is part of the information returned by {@link 
#getIdentificationInfo()}.
++     *
++     * @return empty collection.
 +     */
 +    @Override
 +    public Collection<Format> getResourceFormats() {
 +        return Collections.emptyList();
 +    }
 +
 +    /**
 +     * Category keywords, their type, and reference source.
 +     * This is part of the information returned by {@link 
#getIdentificationInfo()}.
++     *
++     * @return empty collection.
 +     */
 +    @Override
 +    public Collection<Keywords> getDescriptiveKeywords() {
 +        return Collections.emptyList();
 +    }
 +
 +    /**
 +     * Basic information about specific application(s) for which the 
resource(s)
 +     * has/have been or is being used by different users.
 +     * This is part of the information returned by {@link 
#getIdentificationInfo()}.
++     *
++     * @return empty collection.
 +     */
 +    @Override
 +    public Collection<Usage> getResourceSpecificUsages() {
 +        return Collections.emptyList();
 +    }
 +
 +    /**
 +     * Information about constraints which apply to the resource(s).
 +     * This is part of the information returned by {@link 
#getIdentificationInfo()}.
++     *
++     * @return empty collection.
 +     */
 +    @Override
 +    public Collection<Constraints> getResourceConstraints() {
 +        return Collections.emptyList();
 +    }
 +
 +    /**
 +     * @deprecated As of ISO 19115:2014, replaced by {@code 
getAssociatedResources()}.
++     *
++     * @return empty collection.
 +     */
 +    @Override
 +    @Deprecated
 +    public Collection<AggregateInformation> getAggregationInfo() {
 +        return Collections.emptyList();
 +    }
 +
 +    /**
 +     * Description of the resource in the producer's processing environment, 
including items
 +     * such as the software, the computer operating system, file name, and 
the dataset size.
 +     * This is part of the information returned by {@link 
#getIdentificationInfo()}.
++     *
++     * @return {@code null}.
 +     */
 +    @Override
 +    public InternationalString getEnvironmentDescription() {
 +        return null;
 +    }
 +
 +    /**
 +     * Any other descriptive information about the resource.
 +     * This is part of the information returned by {@link 
#getIdentificationInfo()}.
++     *
++     * @return {@code null}.
 +     */
 +    @Override
 +    public InternationalString getSupplementalInformation() {
 +        return null;
 +    }
 +
  
      /* 
-------------------------------------------------------------------------------------------------
       * Implementation of the Citation object returned by 
DataIdentification.getCitation().
@@@ -505,67 -226,14 +594,81 @@@
      /**
       * Name by which the cited resource is known.
       * This is part of the information returned by {@link #getCitation()}.
+      *
 -     * @return null.
++     * @return {@code null}.
       */
      @Override
      public InternationalString getTitle() {
          return null;
      }
  
 +    /**
 +     * Short names or other language names by which the cited information is 
known.
 +     * This is part of the information returned by {@link #getCitation()}.
++     *
++     * @return empty collection.
 +     */
 +    @Override
 +    public Collection<InternationalString> getAlternateTitles() {
 +        return Collections.emptyList();
 +    }
 +
 +    /**
 +     * Reference dates for the cited resource.
 +     * This is part of the information returned by {@link #getCitation()}.
++     *
++     * @return empty collection.
 +     */
 +    @Override
 +    public Collection<CitationDate> getDates() {
 +        return Collections.emptyList();
 +    }
 +
 +    /**
 +     * Version of the cited resource.
 +     * This is part of the information returned by {@link #getCitation()}.
++     *
++     * @return {@code null}.
 +     */
 +    @Override
 +    public InternationalString getEdition() {
 +        return null;
 +    }
 +
 +    /**
 +     * Date of the edition.
 +     * This is part of the information returned by {@link #getCitation()}.
++     *
++     * @return {@code null}.
 +     */
 +    @Override
 +    public Date getEditionDate() {
 +        return null;
 +    }
 +
 +    /**
 +     * Unique identifier for the resource.
 +     * This is part of the information returned by {@link #getCitation()}.
++     *
++     * @return empty collection.
 +     */
 +    @Override
 +    public Collection<Identifier> getIdentifiers() {
 +        return Collections.emptyList();
 +    }
 +
 +    /**
 +     * Role, name, contact and position information for individuals or 
organisations
 +     * that are responsible for the resource.
 +     * This is part of the information returned by {@link #getCitation()}.
++     *
++     * @return empty collection.
 +     */
 +    @Override
 +    public Collection<ResponsibleParty> getCitedResponsibleParties() {
 +        return Collections.emptyList();
 +    }
 +
      /**
       * Mode in which the resource is represented.
       * This is part of the information returned by {@link #getCitation()}.
@@@ -576,49 -246,4 +681,59 @@@
      public Collection<PresentationForm> getPresentationForms() {
          return Collections.singleton(PresentationForm.TABLE_DIGITAL);
      }
 +
 +    /**
 +     * Information about the series, or aggregate dataset, of which the 
dataset is a part.
 +     * This is part of the information returned by {@link #getCitation()}.
++     *
++     * @return {@code null}.
 +     */
 +    @Override
 +    public Series getSeries() {
 +        return null;
 +    }
 +
 +    /**
 +     * Other information required to complete the citation that is not 
recorded elsewhere.
 +     * This is part of the information returned by {@link #getCitation()}.
++     *
++     * @return {@code null}.
 +     */
 +    @Override
 +    public InternationalString getOtherCitationDetails() {
 +        return null;
 +    }
 +
 +    /**
 +     * @deprecated Removed as of ISO 19115:2014.
++     *
++     * @return {@code null}.
 +     */
 +    @Override
 +    @Deprecated
 +    public InternationalString getCollectiveTitle() {
 +        return null;
 +    }
 +
 +    /**
 +     * International Standard Book Number.
 +     * This is part of the information returned by {@link #getCitation()}.
++     *
++     * @return {@code null}.
 +     */
 +    @Override
 +    public String getISBN() {
 +        return null;
 +    }
 +
 +    /**
 +     * International Standard Serial Number.
 +     * This is part of the information returned by {@link #getCitation()}.
++     *
++     * @return {@code null}.
 +     */
 +    @Override
 +    public String getISSN() {
 +        return null;
 +    }
  }
diff --cc 
endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataWriter.java
index 3eb34c344f,b6dcc0d6d5..815317dc4c
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataWriter.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataWriter.java
@@@ -402,12 -407,12 +408,12 @@@ public class MetadataWriter extends Met
           * Once a dependency has been added to the database, the 
corresponding value in
           * the `asMap` HashMap is replaced by the identifier of the 
dependency we just added.
           */
-         Map<String,FKey> referencedTables = null;
-         for (final Map.Entry<String,Object> entry : asSingletons.entrySet()) {
+         Map<String, FKey> referencedTables = null;
+         for (final Map.Entry<String, Object> entry : asSingletons.entrySet()) 
{
              Object value = entry.getValue();
              final Class<?> type = value.getClass();
 -            if (ControlledVocabulary.class.isAssignableFrom(type)) {
 -                value = addCode(stmt, (ControlledVocabulary) value);
 +            if (CodeList.class.isAssignableFrom(type)) {
 +                value = addCode(stmt, (CodeList<?>) value);
              } else if (type.isEnum()) {
                  value = ((Enum<?>) value).name();
              } else if (standard.isMetadata(type)) {
diff --cc 
endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultInstant.java
index 6a48878060,184373610c..290c010337
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultInstant.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultInstant.java
@@@ -259,9 -261,21 +260,21 @@@ cmp:    if (canTestBefore | canTestAfte
              if (canTestAfter  && ((TimeMethods) comparators).isAfter 
.test(t1, t2)) return TemporalOperatorName.AFTER;
              if (canTestEqual  && ((TimeMethods) comparators).isEqual 
.test(t1, t2)) return TemporalOperatorName.EQUALS;
          }
 -        throw new 
IndeterminatePositionException(Errors.format(Errors.Keys.IndeterminatePosition));
 +        throw new 
DateTimeException(Errors.format(Errors.Keys.IndeterminatePosition));
      }
  
+     /**
+      * Returns the standard interface that defines the contract of this class.
+      * This is the base type required by all {@code equals(…)} methods
+      * for returning a potentially {@code true} value.
+      *
+      * @return {@code Instant.class}.
+      */
+     @Override
+     public final Type getStandardType() {
+         return Instant.class;
+     }
+ 
      /**
       * Compares this instant with the given object, optionally ignoring 
timezone.
       * If the comparison mode ignores metadata, this method compares only the 
position on the timeline.
diff --cc 
endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/PropertyConsistencyCheck.java
index c5b04164e3,56a6207a4c..36f1e235ca
--- 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/PropertyConsistencyCheck.java
+++ 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/PropertyConsistencyCheck.java
@@@ -133,7 -138,7 +135,7 @@@ public abstract class PropertyConsisten
              if (type == CodeList.class) {
                  return null;
              }
-             final CodeList<?>[] codes = (CodeList<?>[]) 
type.getMethod("values", (Class[]) null).invoke(null, (Object[]) null);
 -            final var codes = (ControlledVocabulary[]) 
type.getMethod("values", (Class[]) null).invoke(null, (Object[]) null);
++            final var codes = (CodeList<?>[]) type.getMethod("values", 
(Class[]) null).invoke(null, (Object[]) null);
              return codes[random.nextInt(codes.length)];
          } catch (ReflectiveOperationException e) {
              fail(e.toString());
diff --cc 
endorsed/src/org.apache.sis.openoffice/main/org/apache/sis/openoffice/ReferencingFunctions.java
index 788e1f9c6d,0e92c07552..80224970a2
--- 
a/endorsed/src/org.apache.sis.openoffice/main/org/apache/sis/openoffice/ReferencingFunctions.java
+++ 
b/endorsed/src/org.apache.sis.openoffice/main/org/apache/sis/openoffice/ReferencingFunctions.java
@@@ -321,14 -311,8 +320,13 @@@ public class ReferencingFunctions exten
                          } else if (object instanceof CoordinateSystem) {
                              cs = (CoordinateSystem) object;
                          } else {
-                             final Object actual;
 -                            Class<?> actual = 
Classes.getStandardClass(object, Object.class);
 -                            if (actual == Object.class) actual = 
object.getClass();
++                            Object actual;
 +                            if (object instanceof DefaultDatumEnsemble) {
 +                                actual = "DatumEnsemble";
-                             } else if (object instanceof 
AbstractIdentifiedObject) {
-                                 actual = ((AbstractIdentifiedObject) 
object).getInterface();
 +                            } else {
-                                 actual = Classes.getClass(object);
++                                actual = Classes.getStandardClass(object, 
Object.class);
++                                if (actual == Object.class) actual = 
object.getClass();
 +                            }
                              return 
Errors.forLocale(getJavaLocale()).getString(Errors.Keys.UnexpectedTypeForReference_3,
                                      codeOrPath, 
CoordinateReferenceSystem.class, actual);
                          }
diff --cc 
endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/ReferencingByIdentifiers.java
index 9fa0d985cd,440541fbba..8fa075dfdb
--- 
a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/ReferencingByIdentifiers.java
+++ 
b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/ReferencingByIdentifiers.java
@@@ -212,6 -195,20 +212,7 @@@ public abstract class ReferencingByIden
          properties.put(OVERALL_OWNER_KEY, party);
          return properties;
      }
+ 
 -    /**
 -     * Returns the GeoAPI interface that defines the contract of this 
implementation class.
 -     * This is the base type required by {@code equals(…)} methods for 
returning a potentially {@code true} value.
 -     * The default implementation returns {@code 
ReferenceSystemUsingIdentifiers.class}.
 -     *
 -     * @return the GeoAPI interface implemented by this class.
 -     * @since 1.7
 -     */
 -    @Override
 -    public Type getStandardType() {
 -        return ReferenceSystemUsingIdentifiers.class;
 -    }
 -
      /**
       * Property used to characterize the spatial reference system.
       *
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/coordinate/DefaultCoordinateMetadata.java
index 8d9c9ccdcb,4a16dc4448..cc970a5e94
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/coordinate/DefaultCoordinateMetadata.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/coordinate/DefaultCoordinateMetadata.java
@@@ -31,7 -32,10 +31,6 @@@ import org.apache.sis.io.wkt.Formattabl
  import org.apache.sis.io.wkt.Formatter;
  import org.apache.sis.util.ComparisonMode;
  import org.apache.sis.util.LenientComparable;
--import org.apache.sis.util.Utilities;
 -
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.coordinate.CoordinateMetadata;
  
  
  /**
@@@ -40,11 -44,8 +39,11 @@@
   * This default implementation provides <i>Well-Known Text</i> support.
   * It is immutable and serializable if the CRS and epoch are also 
serializable.
   *
 + * <h2>Future evolution</h2>
 + * This class is expected to implement a {@code CoordinateMetadata} interface 
after the next GeoAPI release.
 + *
   * @author  Martin Desruisseaux (Geomatys)
-  * @version 1.5
+  * @version 1.7
   * @since   1.5
   */
  public class DefaultCoordinateMetadata extends FormattableObject
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeocentricCRS.java
index e386038daa,c221b6873b..fcaa557ee0
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeocentricCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeocentricCRS.java
@@@ -77,9 -78,9 +78,9 @@@ import org.apache.sis.referencing.datum
   * in the javadoc, this condition holds if all components were created using 
only SIS factories and static constants.
   *
   * @author  Martin Desruisseaux (IRD, Geomatys)
-  * @version 1.6
+  * @version 1.7
   *
 - * @see 
org.apache.sis.referencing.factory.GeodeticAuthorityFactory#createGeodeticCRS(String)
 + * @see 
org.apache.sis.referencing.factory.GeodeticAuthorityFactory#createGeocentricCRS(String)
   *
   * @since 0.4
   */
@@@ -220,18 -222,14 +221,19 @@@ public class DefaultGeocentricCRS exten
      }
  
      /**
-      * Returns the GeoAPI interface implemented by this class.
-      * The SIS implementation returns {@code GeocentricCRS.class}.
+      * Returns the GeoAPI interface that defines the contract of this 
implementation class.
+      * This is the base type required by {@code equals(…)} methods for 
returning a potentially {@code true} value.
       *
 +     * <h4>Note for implementers</h4>
 +     * Subclasses usually do not need to override this method since GeoAPI 
does not define {@code GeocentricCRS}
 +     * sub-interface. Overriding possibility is left mostly for implementers 
who wish to extend GeoAPI with their
 +     * own set of interfaces.
 +     *
       * @return {@code GeocentricCRS.class} or a user-defined sub-interface.
+      * @since 1.7
       */
      @Override
-     public Class<? extends GeocentricCRS> getInterface() {
+     public Type getStandardType() {
          return GeocentricCRS.class;
      }
  
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java
index 03988ff65c,f8e5089bce..e5386ec38c
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java
@@@ -58,8 -59,13 +59,10 @@@ import org.apache.sis.util.collection.C
  import java.util.Date;
  import org.opengis.referencing.datum.VerticalDatumType;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import java.util.Optional;
 -import java.time.temporal.Temporal;
 -import org.opengis.referencing.crs.ParametricCRS;
 -import org.opengis.referencing.datum.DatumEnsemble;
 -import org.opengis.referencing.datum.ParametricDatum;
 -import org.opengis.referencing.datum.RealizationMethod;
 +// Specific to the main branch:
++import org.opengis.annotation.UML;
++import static org.opengis.annotation.Specification.ISO_19111;
 +import org.opengis.metadata.extent.Extent;
  
  
  /**
@@@ -87,8 -93,7 +90,8 @@@
   *
   * @since 1.5
   */
- @SecondaryTrait(Datum.class)
 -public class DefaultDatumEnsemble<D extends Datum> extends 
AbstractIdentifiedObject implements DatumEnsemble<D>, Datum {
++@UML(identifier="DatumEnsemble", specification=ISO_19111)
 +public class DefaultDatumEnsemble<D extends Datum> extends 
AbstractIdentifiedObject implements Datum {
      /**
       * Serial number for inter-operability with different versions.
       */
@@@ -274,6 -309,23 +277,23 @@@
          return ensemble;
      }
  
+     /**
+      * Returns the GeoAPI interface that defines the contract of this 
implementation class.
+      * This is the base type required by {@code equals(…)} methods for 
returning a potentially {@code true} value.
+      *
+      * @return {@code DatumEnsemble.class} or a user-defined sub-interface.
+      * @since 1.7
+      */
+     @Override
+     public Type getStandardType() {
 -        return new ParameterizedType(DatumEnsemble.class) {
++        return new ParameterizedType(DefaultDatumEnsemble.class) {
+             @Override protected Class<?> getActualTypeArgument() {
+                 Class<? extends Datum>[] types = 
Classes.getLeafInterfaces(DefaultDatumEnsemble.this.getClass(), Datum.class);
+                 return (types.length != 0) ? types[0] : Datum.class;
+             }
+         };
+     }
+ 
      /*
       * NOTE: a previous version provided the following method:
       *
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultParametricDatum.java
index ab7461f32a,f9331426ab..98247c6d25
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultParametricDatum.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultParametricDatum.java
@@@ -127,8 -130,35 +127,8 @@@ public class DefaultParametricDatum ext
          super(datum);
      }
  
 -    /**
 -     * Returns a SIS datum implementation with the same values as the given 
arbitrary implementation.
 -     * If the given object is {@code null}, then this method returns {@code 
null}.
 -     * Otherwise if the given object is already a SIS implementation, then 
the given object is returned unchanged.
 -     * Otherwise a new SIS implementation is created and initialized to the 
attribute values of the given object.
 -     *
 -     * @param  object  the object to get as a SIS implementation, or {@code 
null} if none.
 -     * @return a SIS implementation containing the values of the given object 
(may be the
 -     *         given object itself), or {@code null} if the argument was null.
 -     */
 -    public static DefaultParametricDatum castOrCopy(final ParametricDatum 
object) {
 -        return (object == null) || (object instanceof DefaultParametricDatum) 
?
 -                (DefaultParametricDatum) object : new 
DefaultParametricDatum(object);
 -    }
 -
 -    /**
 -     * Returns the GeoAPI interface that defines the contract of this 
implementation class.
 -     * This is the base type required by {@code equals(…)} methods for 
returning a potentially {@code true} value.
 -     *
 -     * @return {@code ParametricDatum.class} or a user-defined sub-interface.
 -     * @since 1.7
 -     */
 -    @Override
 -    public Type getStandardType() {
 -        return ParametricDatum.class;
 -    }
 -
      /**
-      * Formats this datum as a <cite>Well Known Text</cite> {@code 
ParametricDatum[…]} element.
+      * Formats this datum as a <i>Well Known Text</i> {@code 
ParametricDatum[…]} element.
       *
       * <h4>Compatibility note</h4>
       * {@code ParametricDatum} is defined in the <abbr>WKT</abbr> 2 
specification only.
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/ReferencingUtilities.java
index b897cfdc78,ff67fc7471..76b851ef5e
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/ReferencingUtilities.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/ReferencingUtilities.java
@@@ -144,99 -134,6 +143,51 @@@ public final class ReferencingUtilitie
          return (crs != null) ? getUnit(crs.getCoordinateSystem()) : null;
      }
  
-     /**
-      * Returns the GeoAPI interface implemented by the given object, or the 
implementation class
-      * if the interface is unknown. This method can be used when the base 
type (CRS, CS, Datum…)
-      * is unknown, for example when preparing an error message. If the base 
type is known, then
-      * the method expecting a {@code baseType} argument should be preferred.
-      *
-      * @param  object    the object for which to get the GeoAPI interface, or 
{@code null}.
-      * @return GeoAPI interface or implementation class of the given object, 
or {@code null} if the given object is null.
-      */
-     @SuppressWarnings("unchecked")
-     public static Class<?> getInterface(final Object object) {
-         if (object instanceof AbstractIdentifiedObject) {
-             return ((AbstractIdentifiedObject) object).getInterface();
-         } else {
-             return getInterface(IdentifiedObject.class, (Class) 
Classes.getClass(object));
-         }
-     }
- 
-     /**
-      * Returns the GeoAPI interface implemented by the given object, or the 
implementation class
-      * if the interface is unknown.
-      *
-      * @param  <T>       compile-time value of {@code baseType}.
-      * @param  baseType  parent interface of the desired type.
-      * @param  object    the object for which to get the GeoAPI interface, or 
{@code null}.
-      * @return GeoAPI interface or implementation class of the given object, 
or {@code null} if the given object is null.
-      */
-     public static <T extends IdentifiedObject> Class<? extends T> 
getInterface(final Class<T> baseType, final T object) {
-         if (object instanceof AbstractIdentifiedObject) {
-             return ((AbstractIdentifiedObject) 
object).getInterface().asSubclass(baseType);
-         } else {
-             return getInterface(baseType, Classes.getClass(object));
-         }
-     }
- 
-     /**
-      * Returns the GeoAPI interface implemented by the given class, or the 
class itself if the interface is unknown.
-      *
-      * @param  <T>       compile-time value of {@code baseType}.
-      * @param  baseType  parent interface of the desired type.
-      * @param  type      type of object for which to get the GeoAPI 
interface, or {@code null}.
-      * @return GeoAPI interface or implementation class, or {@code null} if 
the given type is null.
-      */
-     public static <T extends IdentifiedObject> Class<? extends T> 
getInterface(final Class<T> baseType, final Class<? extends T> type) {
-         final Class<? extends T>[] types = Classes.getLeafInterfaces(type, 
baseType);
-         return (types.length != 0) ? types[0] : type;
-     }
- 
 +    /**
 +     * Copies all {@link SingleCRS} components from the given source to the 
given collection.
 +     * For each {@link CompoundCRS} element found in the iteration, this 
method replaces the
 +     * {@code CompoundCRS} by its {@linkplain CompoundCRS#getComponents() 
components}, which
 +     * may themselves have other {@code CompoundCRS}. Those replacements are 
performed recursively
 +     * until we obtain a flat view of CRS components.
 +     *
 +     * @param  source  the collection of single or compound CRS.
 +     * @param  addTo   where to add the single CRS in order to obtain a flat 
view of {@code source}.
 +     * @return {@code true} if this method found only single CRS in {@code 
source}, in which case {@code addTo}
 +     *         got the same content (assuming that {@code addTo} was empty 
prior this method call).
 +     * @throws NoSuchElementException if a CRS component is missing.
 +     * @throws ClassCastException if a CRS is neither a {@link SingleCRS} or 
a {@link CompoundCRS}.
 +     *
 +     * @see 
org.apache.sis.referencing.CRS#getSingleComponents(CoordinateReferenceSystem)
 +     */
 +    public static boolean getSingleComponents(final Iterable<? extends 
CoordinateReferenceSystem> source,
 +            final Collection<? super SingleCRS> addTo) throws 
ClassCastException
 +    {
 +        boolean sameContent = true;
 +        for (final CoordinateReferenceSystem candidate : source) {
 +            if (candidate instanceof CompoundCRS) {
 +                getSingleComponents(((CompoundCRS) 
candidate).getComponents(), addTo);
 +                sameContent = false;
 +            } else if (candidate instanceof SingleCRS) {
 +                addTo.add((SingleCRS) candidate);
 +            } else {
 +                /*
 +                 * Illegal class. Try to provide a better error message, in 
particular when the CRS component
 +                 * is nil because it is an unresolved xlink in a GML 
document. Nil objects are proxies, which
 +                 * have hard to understand class names.
 +                 */
 +                final String message;
 +                if (candidate instanceof NilObject) {
 +                    message = Errors.format(Errors.Keys.NilObject_1, 
Identifiers.getNilReason((NilObject) candidate));
 +                    throw new NoSuchElementException(message);
 +                } else {
-                     message = 
Errors.format(Errors.Keys.NestedElementNotAllowed_1, getInterface(candidate));
++                    message = 
Errors.format(Errors.Keys.NestedElementNotAllowed_1, 
Classes.getStandardType(candidate.getClass()));
 +                    throw new ClassCastException(message);
 +                }
 +            }
 +        }
 +        return sameContent;
 +    }
 +
      /**
       * Returns {@code true} if the type of the given datum is ellipsoidal. A 
vertical datum is not allowed
       * to be ellipsoidal according ISO 19111, but Apache SIS relaxes this 
restriction in some limited cases,
diff --cc 
endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultDatumEnsembleTest.java
index 0000000000,e909944bec..5d3100a16b
mode 000000,100644..100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultDatumEnsembleTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultDatumEnsembleTest.java
@@@ -1,0 -1,99 +1,96 @@@
+ /*
+  * 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.referencing.datum;
+ 
+ import java.util.Map;
+ import java.util.List;
+ import org.opengis.metadata.Identifier;
+ import org.opengis.referencing.datum.GeodeticDatum;
+ import org.apache.sis.referencing.GeodeticException;
+ import org.apache.sis.metadata.KeyNamePolicy;
+ import org.apache.sis.metadata.MetadataStandard;
+ import org.apache.sis.metadata.ValueExistencePolicy;
+ import 
org.apache.sis.metadata.iso.quality.DefaultAbsoluteExternalPositionalAccuracy;
+ 
+ // Test dependencies
+ import org.junit.jupiter.api.Test;
+ import static org.junit.jupiter.api.Assertions.*;
+ import static org.apache.sis.test.Assertions.assertMessageContains;
+ import org.apache.sis.test.TestCase;
+ 
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.referencing.datum.DatumEnsemble;
 -
+ 
+ /**
+  * Tests the {@link DefaultDatumEnsemble} class.
+  *
+  * @author  Martin Desruisseaux (Geomatys)
+  */
+ @SuppressWarnings("exports")
+ public class DefaultDatumEnsembleTest extends TestCase {
+     /**
+      * Creates a new test case.
+      */
+     public DefaultDatumEnsembleTest() {
+     }
+ 
+     /**
+      * Creates a dummy ensemble with <abbr>WGS</abbr> datum.
+      */
+     private static DefaultDatumEnsemble<GeodeticDatum> WGS() {
+         return DefaultDatumEnsemble.create(
+                 Map.of(DefaultDatumEnsemble.NAME_KEY, "Various WGS"),
+                 GeodeticDatum.class,
+                 List.of(HardCodedDatum.WGS84, HardCodedDatum.WGS72),
+                 new DefaultAbsoluteExternalPositionalAccuracy());
+     }
+ 
+     /**
+      * Tests the creation of a datum ensemble with some arbitrary geodetic 
datum.
+      */
+     @Test
+     public void testGeodetic() {
+         final DefaultDatumEnsemble<GeodeticDatum> ensemble = WGS();
+         assertEquals("Various WGS", ensemble.getName().getCode());
+         assertTrue(ensemble.getMembers().contains(HardCodedDatum.WGS84));
+         assertTrue(ensemble.getMembers().contains(HardCodedDatum.WGS72));
+         final GeodeticDatum geodetic = assertInstanceOf(GeodeticDatum.class, 
ensemble);
 -        assertEquals("DatumEnsemble<GeodeticDatum>", 
ensemble.getStandardType().getTypeName());
++        assertEquals("DefaultDatumEnsemble<GeodeticDatum>", 
ensemble.getStandardType().getTypeName());
+         assertEquals(0, geodetic.getPrimeMeridian().getGreenwichLongitude());
+         GeodeticException e = assertThrows(GeodeticException.class, () -> 
geodetic.getEllipsoid());
+         assertMessageContains(e, "WGS");
+     }
+ 
+     /**
+      * Tests the view as a map. This test depends on {@link 
DefaultDatumEnsemble#getStandardType()},
+      * which is invoked by the metadata module for determining which 
interface is the main one.
+      */
+     @Test
+     public void testValueMap() {
+         final Map<String, Object> values = 
MetadataStandard.ISO_19111.asValueMap(
+                 WGS(), null, KeyNamePolicy.UML_IDENTIFIER, 
ValueExistencePolicy.ALL);
+         assertEquals("Various WGS", assertInstanceOf(Identifier.class, 
values.get("name")).getCode());
+     }
+ 
+     /**
+      * Tests another view as a map where the object is specified as a class 
instead of as an instance.
+      */
+     @Test
+     public void testNameMap() {
+         final Map<String, String> names = 
MetadataStandard.ISO_19111.asNameMap(
 -                DatumEnsemble.class, KeyNamePolicy.UML_IDENTIFIER, 
KeyNamePolicy.SENTENCE);
++                DefaultDatumEnsemble.class, KeyNamePolicy.UML_IDENTIFIER, 
KeyNamePolicy.SENTENCE);
+         assertEquals("Name", names.get("name"));
+     }
+ }
diff --cc 
endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/StoreUtilities.java
index 276b4db1e6,f4975053e4..54fcc1b898
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/StoreUtilities.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/StoreUtilities.java
@@@ -53,9 -47,8 +47,8 @@@ import org.apache.sis.system.Configurat
  import org.apache.sis.system.Modules;
  import org.apache.sis.util.resources.Errors;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.feature.Feature;
 +// Specific to the main branch:
 +import org.apache.sis.feature.AbstractFeature;
- import org.apache.sis.metadata.iso.identification.AbstractIdentification;
  
  
  /**
diff --cc 
endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/URIDataStoreOption.java
index 0000000000,186806e78b..1a87520100
mode 000000,100644..100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/URIDataStoreOption.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/URIDataStoreOption.java
@@@ -1,0 -1,333 +1,336 @@@
+ /*
+  * 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.storage.base;
+ 
+ import java.net.URI;
+ import java.nio.file.StandardOpenOption;
+ import java.util.EnumSet;
+ import java.util.Collection;
+ import org.opengis.parameter.ParameterValueGroup;
+ import org.opengis.parameter.ParameterDescriptor;
+ import org.opengis.parameter.ParameterDescriptorGroup;
+ import org.opengis.parameter.ParameterNotFoundException;
+ import org.apache.sis.parameter.ParameterBuilder;
+ import org.apache.sis.storage.DataStoreProvider;
+ import org.apache.sis.storage.OptionKey;
+ import org.apache.sis.storage.StorageConnector;
+ import org.apache.sis.storage.IllegalOpenParameterException;
+ import org.apache.sis.storage.internal.Resources;
+ import org.apache.sis.util.ObjectConverters;
+ import org.apache.sis.util.UnconvertibleObjectException;
+ import org.apache.sis.util.resources.Errors;
+ import org.apache.sis.util.logging.Logging;
+ 
++// Specific to the main branch:
++import org.apache.sis.parameter.DefaultParameterDescriptor;
++
+ 
+ /**
+  * Options supported by {@link URIDataStoreProvider}.
+  * This enumeration does the link between {@link ParameterDescriptor} and 
{@link OptionKey}.
+  * This enumeration contains all parameters used by all subclasses of {@link 
URIDataStore}
+  * in the Apache <abbr>SIS</abbr> code base. This is an enumeration for a 
closed universe,
+  * which is why it cannot be in exported <abbr>API</abbr>.
+  * This enumeration is useful for data stores that are containers of other 
data stores,
+  * such as the data store that read all files in a folder.
+  *
+  * <h4>Deferred construction of parameter descriptor</h4>
+  * Some parameters are used by only one {@link URIDataStore}. These 
parameters are constructed
+  * during the static initialization of the {@link URIDataStoreProvider} that 
use them.
+  *
+  * <h4>Future evolution</h4>
+  * This class is an attempt to bring a little bit of order in the mapping 
between parameters and option keys,
+  * at least for the most frequently used parameters. This class may change in 
any future <abbr>SIS</abbr>
+  * version according experience.
+  *
+  * @author  Johann Sorel (Geomatys)
+  * @author  Martin Desruisseaux (Geomatys)
+  */
+ public enum URIDataStoreOption {
+     /**
+      * Description of the mandatory {@value URIDataStoreProvider#LOCATION} 
parameter.
+      * This is the only mandatory parameter in all {@link 
URIDataStoreProvider} implementations.
+      *
+      * @see URIDataStoreProvider#LOCATION
+      * @see #createForLocationOnly(String)
+      */
+     LOCATION(URIDataStoreProvider.LOCATION, Resources.Keys.DataStoreLocation, 
URI.class),
+ 
+     /**
+      * Description of the optional {@value URIDataStoreProvider#FORMAT} 
parameter.
+      *
+      * @see URIDataStoreProvider#FORMAT
+      */
+     FORMAT(URIDataStoreProvider.FORMAT, 
Resources.Keys.DirectoryContentFormatName, String.class),
+ 
+     /**
+      * Description of the optional {@value URIDataStoreProvider#CREATE} 
parameter of writable data stores.
+      *
+      * @see URIDataStoreProvider#CREATE
+      * @see OptionKey#OPEN_OPTIONS
+      */
+     CREATE(URIDataStoreProvider.CREATE, Resources.Keys.DataStoreCreate, 
Boolean.class),
+ 
+     /**
+      * Description of the optional parameter for character encoding used by 
the data store.
+      * This is used only when the data store does not have explicit encoding 
information.
+      *
+      * @see OptionKey#ENCODING
+      */
+     ENCODING(OptionKey.ENCODING),
+ 
+     /**
+      * Description of the parameter for formatting conventions of dates and 
numbers.
+      * This is used only when the data store does not have explicit locale 
information.
+      *
+      * @see OptionKey#LOCALE
+      */
+     LOCALE(OptionKey.LOCALE),
+ 
+     /**
+      * Description of the optional parameter for the time zone used by the 
data store.
+      * This is used only when the data store does not have explicit time zone 
information.
+      *
+      * @see OptionKey#TIMEZONE
+      */
+     TIMEZONE(OptionKey.TIMEZONE),
+ 
+     /**
+      * Description of the optional parameter for the default coordinate 
reference system.
+      * This is used only when the data store does not have explicit 
<abbr>CRS</abbr> information.
+      *
+      * @see OptionKey#DEFAULT_CRS
+      * @todo Use {@link CodeType} when parsing from text.
+      */
+     DEFAULT_CRS(OptionKey.DEFAULT_CRS),
+ 
+     /**
+      * Description of the optional "metadata" parameter.
+      *
+      * @see OptionKey#METADATA_PATH
+      */
+     METADATA(OptionKey.METADATA_PATH),
+ 
+     /**
+      * Description of the optional parameter for specifying whether to 
assemble
+      * distinct lines into a single {@code Feature} instance forming a 
foliation.
+      */
+     FOLIATION(OptionKey.FOLIATION_REPRESENTATION);
+ 
+     /**
+      * The option key for storing the parameter value in a storage connector,
+      * or {@code null} if none.
+      */
+     private final OptionKey<?> option;
+ 
+     /**
+      * The parameter descriptor for this option, or {@code null} if not yet 
constructed.
+      *
+      * @see #getParameterDescriptor()
+      */
+     private volatile ParameterDescriptor<?> parameter;
+ 
+     /**
+      * Creates a new enumeration value with deferred construction of the 
parameter descriptor.
+      *
+      * @param option  associated option in {@link StorageConnector}.
+      */
+     private URIDataStoreOption(final OptionKey<?> option) {
+         this.option = option;
+     }
+ 
+     /**
+      * Creates a new enumeration value with immediate construction of the 
parameter descriptor.
+      *
+      * @param parameterName  name of the parameter to create.
+      * @param description    constant from {@link Resources} keys for the 
localized description.
+      * @param valueClass     type of value of the parameter.
+      */
+     private <V> URIDataStoreOption(final String parameterName, final short 
description, final Class<V> valueClass) {
+         option = null;
+         parameter = new ParameterBuilder()
+                 .addName(parameterName)
+                 .setDescription(Resources.formatInternational(description))
+                 .setRequired(ordinal() == 0)
+                 .create(valueClass, null);
+     }
+ 
+     /**
+      * Returns the parameter associated to this enumeration.
+      *
+      * @return the parameter (never {@code null}).
+      * @throws IllegalStateException if the parameter has not yet been 
initialized.
+      */
+     public final ParameterDescriptor<?> getParameterDescriptor() {
+         ParameterDescriptor<?> p = parameter;
+         if (p == null) {
+             p = option.asOpenParameter().orElseThrow(() -> new 
IllegalStateException(name()));
+         }
+         return p;
+     }
+ 
+     /**
+      * Returns the parameter name.
+      */
+     private String parameterName() {
+         return getParameterDescriptor().getName().getCode();
+     }
+ 
+     /**
+      * Creates a parameter with the same name as this parameter but different 
type or remarks.
+      *
+      * @param  builder     the builder to use for creating the parameter.
+      * @param  valueClass  the new value class, or {@code null} for keeping 
the same class.
+      * @param  remarks     the new parameter remarks, or {@code null} for 
keeping the same remarks.
+      * @return parameter of the same name as {@link #parameter()} but the 
specified value class and remarks.
+      */
+     public final ParameterDescriptor<?> deriveParameterDescriptor(
+             final ParameterBuilder builder, Class<?> valueClass, CharSequence 
remarks)
+     {
 -        ParameterDescriptor<?> p = getParameterDescriptor();
++        var p = (DefaultParameterDescriptor<?>) getParameterDescriptor();
+         if (valueClass == null) valueClass = p.getValueClass();
+         if (remarks    == null) remarks    = p.getRemarks();
+         return builder.addName(p.getName())
+                 .setDescription(p.getDescription().orElse(null))
+                 .setRemarks(remarks)
+                 .create(valueClass, null);
+     }
+ 
+     /**
+      * Creates a parameter descriptor group containing only the mandatory 
parameters.
+      * The mandatory parameter is: {@link #LOCATION}.
+      *
+      * @param  name  short name of the data store format.
+      * @return the descriptor for open parameters with only mandatory 
parameters.
+      *
+      * @see URIDataStoreProvider#createOpenParameters(ParameterBuilder)
+      */
+     public static ParameterDescriptorGroup createForLocationOnly(final String 
name) {
+         return new 
ParameterBuilder().addName(name).createGroup(LOCATION.getParameterDescriptor());
+     }
+ 
+     /**
+      * Returns a group of parameters set to the given mandatory values.
+      * This is a shortcut when only the mandatory parameters are defined.
+      * If the parameter descriptor group of the given provider contains 
parameters other than location,
+      * those additional parameters are present but without values.
+      *
+      * @param  provider  provider of the data store for which to get the 
parameters, or {@code null}.
+      * @param  location  value of {@link #LOCATION}, or {@code null} if none.
+      * @return the parameters with the given values, or {@code null} if 
{@code provider} or {@code location} is null.
+      */
+     public static ParameterValueGroup createWithLocationOnly(final 
DataStoreProvider provider, final URI location) {
+         if (provider != null && location != null) {
+             final ParameterDescriptorGroup descriptor = 
provider.getOpenParameters();
+             if (descriptor != null) {
+                 final ParameterValueGroup pg = descriptor.createValue();
+                 
pg.parameter(URIDataStoreProvider.LOCATION).setValue(location);
+                 return pg;
+             }
+         }
+         return null;
+     }
+ 
+     /**
+      * Sets the value of the parameter described by this enumeration value.
+      *
+      * @param parameters  the parameters where to set the parameter.
+      * @param value       the value to set, or {@code null} if none.
+      */
+     public final void setValueOf(final ParameterValueGroup parameters, final 
Object value) {
+         if (value != null) {
+             parameters.parameter(parameterName()).setValue(value);
+         }
+     }
+ 
+     /**
+      * Creates a storage connector initialized to the location declared in 
the given parameters.
+      * The {@value #LOCATION} parameter is unconditionally requested. The 
other parameters to be
+      * requested are specified in the {@code options} collection.
+      * Missing optional parameters are ignored.
+      *
+      * @param  provider     the provider for which to create a storage 
connector (for error messages).
+      * @param  parameters   the parameters to use for creating a storage 
connector.
+      * @param  options      options to include, or {@code null} for only the 
mandatory parameters.
+      * @param  openOptions  where to store open options, or {@code null} for 
storing them in the storage connector.
+      * @return the storage connector initialized to the location specified in 
the parameters.
+      * @throws IllegalOpenParameterException if no {@value 
URIDataStoreProvider#LOCATION} parameter has been found.
+      */
+     public static StorageConnector connector(final DataStoreProvider provider,
+                                              final ParameterValueGroup 
parameters,
+                                              Collection<URIDataStoreOption> 
options,
+                                              EnumSet<StandardOpenOption> 
openOptions)
+             throws IllegalOpenParameterException
+     {
+         final StorageConnector connector;
+         try {
+             connector = new 
StorageConnector(parameters.parameter(URIDataStoreProvider.LOCATION).getValue());
+         } catch (ParameterNotFoundException | NullPointerException e) {
+             throw new IllegalOpenParameterException(
+                     Resources.format(Resources.Keys.UndefinedParameter_2,
+                                      provider.getShortName(),
+                                      URIDataStoreProvider.LOCATION), e);
+         }
+         if (options != null) {
+             for (final URIDataStoreOption option : options) {
+                 if (option.option == null) continue;
+                 final Object value;
+                 try {
+                     value = 
parameters.parameter(option.parameterName()).getValue();
+                 } catch (ParameterNotFoundException e) {
+                     Logging.ignorableException(provider.getLogger(), 
URIDataStoreOption.class, "connector", e);
+                     continue;
+                 }
+                 if (value != null) try {
+                     switch (option) {
+                         case CREATE: {
+                             final boolean store = (openOptions == null);
+                             if (store) {
+                                 openOptions = 
EnumSet.of(StandardOpenOption.WRITE);
+                             } else {
+                                 openOptions.add(StandardOpenOption.WRITE);
+                             }
+                             if (ObjectConverters.convert(value, 
Boolean.class)) {
+                                 openOptions.add(StandardOpenOption.CREATE);
+                             }
+                             if (store) {
+                                 connector.setOption(OptionKey.OPEN_OPTIONS, 
openOptions.toArray(StandardOpenOption[]::new));
+                             }
+                             break;
+                         }
+                         default: {
+                             convertAndSet(option.option, value, connector);
+                             break;
+                         }
+                     }
+                 } catch (UnconvertibleObjectException e) {
+                     throw new IllegalOpenParameterException(Errors.format(
+                             Errors.Keys.IllegalOptionValue_2, 
option.parameterName(), value), e);
+                 }
+             }
+         }
+         return connector;
+     }
+ 
+     /**
+      * Set the given option to the given value in the given storage connector.
+      * Defined as a separated method for type-safety.
+      */
+     private static <V> void convertAndSet(final OptionKey<V> option, final 
Object value, final StorageConnector connector) {
+         connector.setOption(option, ObjectConverters.convert(value, 
option.getElementType()));
+     }
+ }
diff --cc 
endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java
index ce216b3c6f,a3d55d71a3..e2d86bb732
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java
@@@ -207,13 -212,12 +210,12 @@@ final class Store extends URIDataStore 
       * All parsed moving features, or {@code null} if none or if not yet 
parsed. If {@link #dissociate}
       * is {@code false}, then this list will be created by {@link 
#features(boolean)} when first needed.
       */
 -    private transient List<Feature> movingFeatures;
 +    private transient List<AbstractFeature> movingFeatures;
  
      /**
-      * Creates a new CSV store from the given file, URL or stream.
-      *
-      * <p>If the CSV file is known to be a Moving Feature file, then the 
given connector should
-      * have an {@link OptionKey#ENCODING} value set to UTF-8.</p>
+      * Creates a new <abbr>CSV</abbr> store from the given file, 
<abbr>URL</abbr> or stream.
+      * If the <abbr>CSV</abbr> file is known to be a Moving Feature file, 
then the given
+      * connector should have an {@link OptionKey#ENCODING} value set to 
<abbr>UTF</abbr>-8.
       *
       * @param  provider   the factory that created this {@code DataStore} 
instance, or {@code null} if unspecified.
       * @param  connector  information about the storage (URL, stream, 
<i>etc</i>).
diff --cc endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Classes.java
index 9f6bb010c8,13c3175627..cc142b6cd2
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Classes.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Classes.java
@@@ -34,8 -35,7 +35,10 @@@ import java.lang.reflect.ParameterizedT
  import java.lang.reflect.Modifier;
  import org.opengis.annotation.UML;
  import org.apache.sis.pending.jdk.JDK12;
- import org.apache.sis.pending.jdk.JDK19;
+ 
++// Specific to the main branch:
++import org.opengis.util.InternationalString;
 +
  
  /**
   * Static methods working on {@link Class} objects.
@@@ -346,21 -364,72 +367,75 @@@ public final class Classes 
      }
  
      /**
-      * Returns the first type or super-type (including interface) considered 
"standard" in Apache SIS sense.
-      * This method applies the following heuristic rules, in that order:
+      * Returns the "standard" (in Apache <abbr>SIS</abbr> sense) interface or 
class of the given object.
+      * The {@code baseType} argument narrows the search for handling the case 
where many standard interfaces are
+      * implemented by the same class (e.g., <abbr>CRS</abbr> and 
<abbr>CS</abbr> may be implemented together).
+      * This method applies the following heuristic rules, in that order and 
ignoring rules that do not apply:
+      *
+      * <ul>
+      *   <li>The class or raw-type declared by {@link 
LenientComparable#getStandardType()}.</li>
+      *   <li>Otherwise, the first interface assignable to {@code baseType} 
which has the {@link UML} annotation.</li>
+      *   <li>Otherwise, {@link Object#getClass()} if that class {@linkplain 
#isPublic(Class) is public}.</li>
+      *   <li>Otherwise, the first public parent class assignable to {@code 
baseType}.</li>
+      *   <li>Otherwise, {@code baseType}.</li>
+      * </ul>
+      *
+      * Those heuristic rules may be adjusted in any future Apache 
<abbr>SIS</abbr> version.
+      *
+      * @param  <T>       the compile-time value of the {@code baseType} 
argument.
+      * @param  object    the object for which to get the standard interface 
or class, or {@code null}.
+      * @param  baseType  the base type of the desired type (e.g., {@code 
CoordinateReferenceSystem.class}).
+      * @return the standard type implemented by {@code object}, or {@code 
null} if {@code object} is null.
+      * @throws NullPointerException if {@code baseType} is null.
+      * @throws ClassCastException if the condition documented in {@link 
LenientComparable#getStandardType()} is violated,
+      *         or if {@code object} is not an instance of {@code baseType} 
(violation of this method parameterized type).
+      *
+      * @since 1.7
+      */
+     public static <T> Class<? extends T> getStandardClass(final T object, 
final Class<T> baseType) {
+         if (object instanceof LenientComparable) {
+             final Class<?> type = getRawClass(((LenientComparable) 
object).getStandardType());
+             if (type != null) {
+                 return type.asSubclass(baseType);
+             }
+         }
+         Class<?> type = getClass(object);
+         if (baseType.isInterface() || baseType == Object.class) {
+             for (final Class<?> candidate : getInterfaceSet(type, baseType)) {
 -                if (candidate.isAnnotationPresent(UML.class)) {
++                if (candidate.isAnnotationPresent(UML.class)
++                        // special cases for UML annotation not present in 
GeoAPI 3.0.
++                        || 
InternationalString.class.isAssignableFrom(candidate))
++                {
+                     return candidate.asSubclass(baseType);
+                 }
+             }
+         }
+         while (type != null) {
+             if (isPublic(type)) {
+                 return type.asSubclass(baseType);
+             }
+             type = type.getSuperclass();
+             if (!baseType.isAssignableFrom(type)) break;
+         }
+         return baseType;
+     }
+ 
+     /**
+      * Returns the "standard" (in Apache <abbr>SIS</abbr> sense) interface or 
class of the given implementation class.
+      * This method applies the following heuristic rules, in that order and 
ignoring rules that do not apply:
       *
       * <ul>
-      *   <li>If the given type implements at least one interface having the 
{@link UML} annotation,
-      *       then the first annotated interface is returned.</li>
-      *   <li>Otherwise the first public class or parent class is 
returned.</li>
+      *   <li>The first interface extended or implemented by {@code type} 
which has the {@link UML} annotation.</li>
+      *   <li>Otherwise, {@code type} if that class {@linkplain 
#isPublic(Class) is public}.</li>
+      *   <li>Otherwise, the first public parent class.</li>
+      *   <li>Otherwise, {@code type}.</li>
       * </ul>
       *
-      * Those heuristic rules may be adjusted in any future Apache SIS version.
+      * Those heuristic rules may be adjusted in any future Apache 
<abbr>SIS</abbr> version.
       *
       * @param  <T>   the compile-time type argument.
-      * @param  type  the type for which to get the standard interface or 
class. May be {@code null}.
-      * @return a standard interface implemented by {@code type}, or otherwise 
the most specific public class.
-      *         Is {@code null} if the given {@code type} argument was null.
+      * @param  type  the type for which to get the standard interface or 
class, or {@code null}.
+      * @return the standard type assignable from {@code type}, or {@code 
type} (possibly null) if none.
       *
       * @since 1.0
       */
diff --cc 
endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/IndexedResourceBundle.java
index e8d872ba94,2495ccd6e4..ce5595a0c7
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/IndexedResourceBundle.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/IndexedResourceBundle.java
@@@ -413,8 -417,10 +414,10 @@@ public abstract class IndexedResourceBu
                  replacement = ((URI) element).getSchemeSpecificPart();      
// For decoding encoded characters.
              } else if (element instanceof Class<?>) {
                  replacement = Classes.getShortName(getPublicType((Class<?>) 
element));
+             } else if (element instanceof Type) {
+                 replacement = ((Type) element).getTypeName();
 -            } else if (element instanceof ControlledVocabulary) {
 -                replacement = 
MetadataServices.getInstance().getCodeTitle((ControlledVocabulary) element, 
getLocale());
 +            } else if (element instanceof CodeList<?>) {
 +                replacement = 
MetadataServices.getInstance().getCodeTitle((CodeList<?>) element, getLocale());
              } else if (element instanceof Range<?>) {
                  final Range<?> range = (Range<?>) element;
                  replacement = new RangeFormat(getLocale(), 
range.getElementType()).format(range);
diff --cc 
endorsed/src/org.apache.sis.util/test/org/apache/sis/util/ClassesTest.java
index 26024dd249,0350add5ba..05c6610296
--- a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/ClassesTest.java
+++ b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/ClassesTest.java
@@@ -46,6 -46,8 +46,7 @@@ import java.io.Serializable
  import java.awt.geom.Point2D;
  import javax.print.attribute.standard.PrinterStateReason;
  import javax.print.attribute.standard.PrinterStateReasons;
+ import org.opengis.util.InternationalString;
 -import org.opengis.metadata.extent.Extent;
  import org.opengis.referencing.IdentifiedObject;
  import org.opengis.referencing.ReferenceSystem;
  import org.opengis.referencing.cs.EllipsoidalCS;
diff --cc 
incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileStore.java
index 26da5ef39f,b3c1e6b611..7cd55f97a7
--- 
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileStore.java
+++ 
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileStore.java
@@@ -61,11 -60,10 +60,10 @@@ import org.locationtech.jts.geom.Polygo
  import org.opengis.geometry.Envelope;
  import org.opengis.metadata.Metadata;
  import org.opengis.metadata.Identifier;
- import org.opengis.parameter.ParameterValueGroup;
  import org.opengis.referencing.crs.CoordinateReferenceSystem;
  import org.opengis.referencing.operation.TransformException;
 +import org.opengis.util.FactoryException;
 +import org.opengis.util.GenericName;
  import org.apache.sis.geometry.ImmutableEnvelope;
  import org.apache.sis.geometry.Envelopes;
  import org.apache.sis.geometry.GeneralEnvelope;
@@@ -350,12 -321,11 +319,11 @@@ public final class ShapefileStore exten
          }
  
          @Override
 -        public synchronized FeatureType getType() throws DataStoreException {
 +        public synchronized DefaultFeatureType getType() throws 
DataStoreException {
              if (type == null) {
-                 if (!Files.isRegularFile(shpPath)) {
+                 if (!Files.isRegularFile(locationAsPath)) {
                      throw new DataStoreException("Shape files do not exist. 
Update FeatureType first to initialize this empty datastore");
                  }
- 
                  final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
                  ftb.setName(files.baseName);
  
@@@ -711,13 -679,12 +679,12 @@@
          }
  
          @Override
 -        public synchronized void updateType(FeatureType newType) throws 
DataStoreException {
 +        public synchronized void updateType(DefaultFeatureType newType) 
throws DataStoreException {
  
              if (!isDefaultView()) throw new DataStoreException("Resource not 
writable in current filter state");
-             if (Files.exists(shpPath)) {
+             if (Files.exists(locationAsPath)) {
                  throw new DataStoreException("Update type is possible only 
when files do not exist. It can be used to create a new shapefile but not to 
update one.");
              }
- 
              final Class<?>[] supportedDateTypes;    // All types other than 
the one at index 0 will lost information.
              if (timezone == null) {
                  supportedDateTypes = new Class<?>[] {
@@@ -736,12 -703,12 +703,12 @@@
                  final DBFHeader dbfHeader = new DBFHeader();
                  dbfHeader.lastUpdate = LocalDate.now();
                  dbfHeader.fields = new DBFField[0];
-                 final Charset charset = userDefinedCharSet == null ? 
StandardCharsets.UTF_8 : userDefinedCharSet;
+                 final Charset charset = (encoding == null) ? 
StandardCharsets.UTF_8 : encoding;
                  CoordinateReferenceSystem crs = 
CommonCRS.WGS84.normalizedGeographic();
  
 -                for (PropertyType pt : newType.getProperties(true)) {
 -                    if (pt instanceof AttributeType) {
 -                        final AttributeType at = (AttributeType) pt;
 +                for (AbstractIdentifiedType pt : newType.getProperties(true)) 
{
 +                    if (pt instanceof DefaultAttributeType) {
 +                        final DefaultAttributeType at = 
(DefaultAttributeType) pt;
                          final Class valueClass = at.getValueClass();
                          final String attName = at.getName().tip().toString();
  
@@@ -858,10 -825,11 +825,11 @@@
          }
  
          @Override
 -        public void add(Iterator<? extends Feature> features) throws 
DataStoreException {
 +        public void add(Iterator<? extends AbstractFeature> features) throws 
DataStoreException {
              if (!isDefaultView()) throw new DataStoreException("Resource not 
writable in current filter state");
-             if (!Files.exists(shpPath)) throw new 
DataStoreException("FeatureType do not exist, use updateType before modifying 
features.");
- 
+             if (!Files.exists(locationAsPath)) {
+                 throw new DataStoreException("FeatureType do not exist, use 
updateType before modifying features.");
+             }
              final Writer writer = new Writer(charset);
              try {
                  //write existing features
@@@ -889,10 -857,11 +857,11 @@@
          }
  
          @Override
 -        public void removeIf(Predicate<? super Feature> filter) throws 
DataStoreException {
 +        public void removeIf(Predicate<? super AbstractFeature> filter) 
throws DataStoreException {
              if (!isDefaultView()) throw new DataStoreException("Resource not 
writable in current filter state");
-             if (!Files.exists(shpPath)) throw new 
DataStoreException("FeatureType do not exist, use updateType before modifying 
features.");
- 
+             if (!Files.exists(locationAsPath)) {
+                 throw new DataStoreException("FeatureType do not exist, use 
updateType before modifying features.");
+             }
              final Writer writer = new Writer(charset);
              try {
                  //write existing features not matching filter
@@@ -914,10 -883,11 +883,11 @@@
          }
  
          @Override
 -        public void replaceIf(Predicate<? super Feature> filter, 
UnaryOperator<Feature> updater) throws DataStoreException {
 +        public void replaceIf(Predicate<? super AbstractFeature> filter, 
UnaryOperator<AbstractFeature> updater) throws DataStoreException {
              if (!isDefaultView()) throw new DataStoreException("Resource not 
writable in current filter state");
-             if (!Files.exists(shpPath)) throw new 
DataStoreException("FeatureType do not exist, use updateType before modifying 
features.");
- 
+             if (!Files.exists(locationAsPath)) {
+                 throw new DataStoreException("FeatureType do not exist, use 
updateType before modifying features.");
+             }
              final Writer writer = new Writer(charset);
              try {
                  //write existing features applying modifications
diff --cc 
incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/ShapefileStoreTest.java
index beac409aaf,3f8586bc93..9171163a1c
--- 
a/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/ShapefileStoreTest.java
+++ 
b/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/ShapefileStoreTest.java
@@@ -63,10 -74,10 +72,10 @@@ public class ShapefileStoreTest 
      @Test
      public void testStream() throws URISyntaxException, DataStoreException {
          final URL url = 
ShapefileStoreTest.class.getResource("/org/apache/sis/storage/shapefile/point.shp");
-         try (final ShapefileStore store = new 
ShapefileStore(Paths.get(url.toURI()))) {
+         try (final ShapefileStore store = create(url)) {
  
              //check feature type
 -            final FeatureType type = store.getType();
 +            final DefaultFeatureType type = store.getType();
              assertEquals("point", type.getName().toString());
              assertEquals(9, type.getProperties(true).size());
              assertNotNull(type.getProperty("sis:identifier"));
@@@ -118,9 -129,9 +127,9 @@@
      @Test
      public void testEnvelopeFilter() throws URISyntaxException, 
DataStoreException {
          final URL url = 
ShapefileStoreTest.class.getResource("/org/apache/sis/storage/shapefile/point.shp");
-         try (final ShapefileStore store = new 
ShapefileStore(Paths.get(url.toURI()))) {
+         try (final ShapefileStore store = create(url)) {
  
 -            final FilterFactory<Feature, Object, Object> ff = 
DefaultFilterFactory.forFeatures();
 +            final DefaultFilterFactory<AbstractFeature, Object, Object> ff = 
DefaultFilterFactory.forFeatures();
  
              final GeneralEnvelope env = new 
GeneralEnvelope(CommonCRS.WGS84.normalizedGeographic());
              env.setRange(0, 2, 3);
@@@ -249,8 -260,8 +258,8 @@@
      @Test
      public void testAddFeatures(@TempDir final Path folder) throws 
URISyntaxException, DataStoreException, IOException {
          final Path temp = folder.resolve("test.shp");
-         try (final ShapefileStore store = new ShapefileStore(temp)) {
+         try (final ShapefileStore store = create(temp)) {
 -            FeatureType type = createType();
 +            DefaultFeatureType type = createType();
              store.updateType(type);
              type = store.getType();
  
@@@ -271,12 -282,12 +280,12 @@@
      @Test
      public void testRemoveFeatures(@TempDir final Path folder) throws 
DataStoreException, IOException {
          final Path temp = folder.resolve("test.shp");
-         try (final ShapefileStore store = new ShapefileStore(temp)) {
+         try (final ShapefileStore store = create(temp)) {
 -            FeatureType type = createType();
 +            DefaultFeatureType type = createType();
              store.updateType(type);
              type = store.getType();
 -            Feature feature1 = createFeature1(type);
 -            Feature feature2 = createFeature2(type);
 +            AbstractFeature feature1 = createFeature1(type);
 +            AbstractFeature feature2 = createFeature2(type);
              store.add(List.of(feature1, feature2).iterator());
  
              //remove first feature
@@@ -299,12 -310,12 +308,12 @@@
      @Test
      public void testReplaceFeatures(@TempDir final Path folder) throws 
DataStoreException, IOException {
          final Path temp = folder.resolve("test.shp");
-         try (final ShapefileStore store = new ShapefileStore(temp)) {
+         try (final ShapefileStore store = create(temp)) {
 -            FeatureType type = createType();
 +            DefaultFeatureType type = createType();
              store.updateType(type);
              type = store.getType();
 -            Feature feature1 = createFeature1(type);
 -            Feature feature2 = createFeature2(type);
 +            AbstractFeature feature1 = createFeature1(type);
 +            AbstractFeature feature2 = createFeature2(type);
              store.add(List.of(feature1, feature2).iterator());
  
              //remove first feature
@@@ -332,17 -343,17 +341,17 @@@
      @Test
      public void testGeneratedId() throws DataStoreException, IOException, 
URISyntaxException {
          final URL url = 
ShapefileStoreTest.class.getResource("/org/apache/sis/storage/shapefile/noid.shp");
-         try (final ShapefileStore store = new 
ShapefileStore(Paths.get(url.toURI()))) {
+         try (final ShapefileStore store = create(url)) {
  
 -            final FeatureType type = store.getType();
 -            final PropertyType generatedID = 
type.getProperty(AttributeConvention.IDENTIFIER);
 -            assertTrue(generatedID instanceof AttributeType);
 +            final DefaultFeatureType type = store.getType();
 +            final var generatedID = 
type.getProperty(AttributeConvention.IDENTIFIER);
 +            assertTrue(generatedID instanceof DefaultAttributeType);
              assertEquals(5, type.getProperties(true).size());
  
 -            try (Stream<Feature> stream = store.features(false)) {
 -                Iterator<Feature> iterator = stream.iterator();
 +            try (Stream<AbstractFeature> stream = store.features(false)) {
 +                Iterator<AbstractFeature> iterator = stream.iterator();
                  assertTrue(iterator.hasNext());
 -                Feature feature1 = iterator.next();
 +                AbstractFeature feature1 = iterator.next();
                  assertEquals("noid.1", 
feature1.getPropertyValue(AttributeConvention.IDENTIFIER));
                  assertEquals("some text", feature1.getPropertyValue("text"));
  
diff --cc 
incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/Snippets.java
index f76cb70704,8cc67a88d7..e5e8c80501
--- 
a/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/Snippets.java
+++ 
b/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/Snippets.java
@@@ -29,11 -29,13 +29,12 @@@ import org.apache.sis.geometry.GeneralE
  import org.apache.sis.referencing.CommonCRS;
  import org.apache.sis.storage.DataStoreException;
  import org.apache.sis.storage.FeatureQuery;
+ import org.apache.sis.storage.StorageConnector;
  
 -// 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.BinarySpatialOperator;
 -import org.opengis.filter.FilterFactory;
 +// Specific to the main branch:
 +import org.apache.sis.feature.AbstractFeature;
 +import org.apache.sis.feature.DefaultFeatureType;
 +import org.apache.sis.filter.Filter;
  
  
  /**
diff --cc 
optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/FilterByDatum.java
index 66377022fe,22884a35b0..8e2648cf3e
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/FilterByDatum.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/FilterByDatum.java
@@@ -34,12 -34,8 +34,11 @@@ import org.opengis.referencing.crs.Vert
  import org.opengis.referencing.crs.EngineeringCRS;
  import org.apache.sis.util.Classes;
  import org.apache.sis.referencing.CRS;
- import org.apache.sis.referencing.internal.shared.ReferencingUtilities;
  import org.apache.sis.util.Utilities;
  
 +// Specific to the main branch:
 +import static 
org.apache.sis.pending.geoapi.referencing.MissingMethods.getDatumEnsemble;
 +
  
  /**
   * Filter of reference systems that are compatible with the data to render.

Reply via email to