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

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 292697f58c2f01450b0fcd85c75d29492b7cb6bd
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Thu Mar 13 18:33:15 2025 +0100

    Improve the SQL store metadata with information about the spatial schema, 
database version and (if applicable) PostGIS version.
---
 .../apache/sis/storage/landsat/MetadataReader.java |  16 ++--
 .../sis/storage/netcdf/ucar/DecoderWrapper.java    |   3 +-
 .../main/org/apache/sis/storage/sql/SQLStore.java  |  25 +++--
 .../apache/sis/storage/sql/feature/Database.java   |  27 ++++++
 .../sis/storage/sql/feature/SpatialSchema.java     |  16 +++-
 .../apache/sis/storage/sql/postgis/Postgres.java   |  15 +++
 .../sis/storage/sql/postgis/PostgresTest.java      |  25 ++++-
 .../apache/sis/storage/base/MetadataBuilder.java   | 103 +++++++++++++++------
 .../main/org/apache/sis/storage/csv/Store.java     |   2 +-
 .../apache/sis/storage/image/WorldFileStore.java   |   4 +-
 .../org/apache/sis/util/resources/Vocabulary.java  |   5 +
 .../sis/util/resources/Vocabulary.properties       |   1 +
 .../sis/util/resources/Vocabulary_fr.properties    |   1 +
 .../org/apache/sis/storage/gdal/GDALStore.java     |   4 +-
 14 files changed, 188 insertions(+), 59 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/MetadataReader.java
 
b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/MetadataReader.java
index a4cf6a0906..110830281d 100644
--- 
a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/MetadataReader.java
+++ 
b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/MetadataReader.java
@@ -340,7 +340,7 @@ final class MetadataReader extends MetadataBuilder {
      * @throws  NumberFormatException if the given value cannot be parsed.
      */
     private Double parseDouble(final String value) throws 
NumberFormatException {
-        return shared(Double.valueOf(value));
+        return shared(Double.parseDouble(value));
     }
 
     /**
@@ -910,16 +910,16 @@ final class MetadataReader extends MetadataBuilder {
          * Set information about all non-null bands. The bands are categorized 
in three groups:
          * PANCHROMATIC, REFLECTIVE and THERMAL.
          */
-        final DefaultCoverageDescription content = 
(DefaultCoverageDescription) singletonOrNull(result.getContentInfo());
+        final var content = (DefaultCoverageDescription) 
singletonOrNull(result.getContentInfo());
         if (content != null) {
-            final EnumMap<BandGroupName,DefaultAttributeGroup> groups = new 
EnumMap<>(BandGroupName.class);
+            final var groups = new 
EnumMap<BandGroupName,DefaultAttributeGroup>(BandGroupName.class);
             for (final EnumMap.Entry<BandName,Band> entry : bands.entrySet()) {
-                final DefaultAttributeGroup group = 
groups.computeIfAbsent(entry.getKey().group, (k) -> {
-                    DefaultAttributeGroup g = new 
DefaultAttributeGroup(CoverageContentType.PHYSICAL_MEASUREMENT, null);
-                    content.getAttributeGroups().add(g);
-                    return g;
+                final DefaultAttributeGroup g = 
groups.computeIfAbsent(entry.getKey().group, (k) -> {
+                    var ag = new 
DefaultAttributeGroup(CoverageContentType.PHYSICAL_MEASUREMENT, null);
+                    content.getAttributeGroups().add(ag);
+                    return ag;
                 });
-                group.getAttributes().add(entry.getValue().sampleDimension);
+                g.getAttributes().add(entry.getValue().sampleDimension);
             }
         }
         result.transitionTo(DefaultMetadata.State.FINAL);
diff --git 
a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/ucar/DecoderWrapper.java
 
b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/ucar/DecoderWrapper.java
index 5f813e9e2f..479018bbd3 100644
--- 
a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/ucar/DecoderWrapper.java
+++ 
b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/ucar/DecoderWrapper.java
@@ -50,7 +50,6 @@ import org.apache.sis.util.collection.TreeTable;
 import org.apache.sis.util.collection.TableColumn;
 import org.apache.sis.metadata.sql.MetadataSource;
 import org.apache.sis.metadata.sql.MetadataStoreException;
-import org.apache.sis.referencing.ImmutableIdentifier;
 import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.base.MetadataBuilder;
@@ -216,7 +215,7 @@ public final class DecoderWrapper extends Decoder 
implements CancelTask {
         } catch (MetadataStoreException e) {
             provider = null;
         }
-        builder.addFormatReader(new ImmutableIdentifier(provider, "UCAR", 
Constants.NETCDF), getVersion());
+        builder.addFormatReader((provider != null) ? provider.getTitle() : 
Constants.NETCDF, getVersion());
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SQLStore.java
 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SQLStore.java
index 6f53770f48..6e2f0361b9 100644
--- 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SQLStore.java
+++ 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SQLStore.java
@@ -30,7 +30,6 @@ import java.lang.reflect.Method;
 import org.opengis.util.GenericName;
 import org.opengis.metadata.Metadata;
 import org.opengis.parameter.ParameterValueGroup;
-import org.opengis.metadata.spatial.SpatialRepresentationType;
 import org.apache.sis.storage.Resource;
 import org.apache.sis.storage.Aggregate;
 import org.apache.sis.storage.DataStore;
@@ -83,11 +82,9 @@ public abstract class SQLStore extends DataStore implements 
Aggregate {
      * @see #getMetadata()
      */
     private static final String[] TITLE_GETTERS = {
-            "getDescription",           // PostgreSQL, SQL Server
+            "getApplicationName",       // PostgreSQL
             "getDataSourceName",        // Derby
-            "getDatabaseName",          // Derby, PostgreSQL, SQL Server, 
SQLite
-            "getUrl",                   // PostgreSQL, SQLite
-            "getURL"                    // SQL Server
+            "getDatabaseName"           // Derby, PostgreSQL, SQL Server, 
SQLite
     };
 
     /**
@@ -101,6 +98,18 @@ public abstract class SQLStore extends DataStore implements 
Aggregate {
             "getDataSourceName"
     };
 
+    /**
+     * Names of possible public getter methods for fetching additional 
citation details.
+     * Elements are sorted in preference order. The return value shall be a 
{@link String}.
+     * On PostgreSQL, the return value is a hard-coded string such as
+     * "Non-Pooling DataSource from PostgreSQL JDBC Driver 42.7.3"
+     *
+     * @see #getIdentifier()
+     */
+    private static final String[] DETAILS_GETTERS = {
+            "getDescription"            // PostgreSQL, SQL Server
+    };
+
     /**
      * The data source to use for obtaining connections to the database.
      * This is the storage given (indirectly, through the {@link 
StorageConnector} argument) at construction time.
@@ -417,14 +426,14 @@ public abstract class SQLStore extends DataStore 
implements Aggregate {
     public synchronized Metadata getMetadata() throws DataStoreException {
         if (metadata == null) {
             final var builder = new MetadataBuilder();
-            
builder.addSpatialRepresentation(SpatialRepresentationType.TEXT_TABLE);
+            builder.addIdentifier(identifier, MetadataBuilder.Scope.RESOURCE);
+            getDataSourceProperty(TITLE_GETTERS).ifPresent(builder::addTitle);
+            
getDataSourceProperty(DETAILS_GETTERS).ifPresent(builder::addOtherCitationDetails);
             try (Connection c = source.getConnection()) {
                 model(c).metadata(c.getMetaData(), builder);
             } catch (Exception e) {
                 throw cannotExecute(e);
             }
-            getDataSourceProperty(TITLE_GETTERS).ifPresent(builder::addTitle);
-            builder.addIdentifier(identifier, MetadataBuilder.Scope.ALL);
             metadata = builder.buildAndFreeze();
         }
         return metadata;
diff --git 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Database.java
 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Database.java
index dd98a4f350..983db64e53 100644
--- 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Database.java
+++ 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Database.java
@@ -57,6 +57,8 @@ import org.apache.sis.util.Debug;
 import org.apache.sis.util.collection.TreeTable;
 import org.apache.sis.util.collection.Cache;
 import org.apache.sis.util.privy.UnmodifiableArrayList;
+import org.apache.sis.util.resources.Vocabulary;
+import org.opengis.metadata.citation.PresentationForm;
 
 
 /**
@@ -465,6 +467,8 @@ public class Database<G> extends Syntax  {
      * @throws SQLException if an error occurred while fetching table 
information.
      */
     public final void metadata(final DatabaseMetaData metadata, final 
MetadataBuilder builder) throws SQLException {
+        builder.addPresentationForm(PresentationForm.TABLE_DIGITAL);
+        builder.addSpatialRepresentation(SpatialRepresentationType.TEXT_TABLE);
         if (hasGeometry) {
             builder.addSpatialRepresentation(SpatialRepresentationType.VECTOR);
         }
@@ -474,6 +478,29 @@ public class Database<G> extends Syntax  {
         for (final Table table : tables) {
             builder.addFeatureType(table.featureType, 
table.countRows(metadata, false, false));
         }
+        builder.addFormatName((spatialSchema != null) ? spatialSchema.name : 
"SQL database");
+        String server = metadata.getDatabaseProductName();
+        if (server != null && !server.isBlank()) {
+            CharSequence description = server;
+            final String version = metadata.getDatabaseProductVersion();
+            if (version != null) {
+                description = 
Vocabulary.formatInternational(Vocabulary.Keys.Version_2, server, version);
+            }
+            
builder.addFormatCitationDetails(completeDatabaseVersion(description));
+        }
+        builder.addFormatReaderSIS("SQL");      // Value of 
SQLStoreProvider.NAME.
+    }
+
+    /**
+     * Completes the given database version with information such as the 
version of the geospatial extension.
+     * For example, in the case of the PostgreSL database, this will add the 
PostGIS version.
+     * The default implementation returns the given text unchanged.
+     *
+     * @param  version  name and version of the database.
+     * @return given text, optionally completed with additional information.
+     */
+    protected CharSequence completeDatabaseVersion(CharSequence version) {
+        return version;
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SpatialSchema.java
 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SpatialSchema.java
index 712eeb0e36..d4f2b34b9a 100644
--- 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SpatialSchema.java
+++ 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SpatialSchema.java
@@ -36,11 +36,12 @@ import java.util.Map;
  */
 public enum SpatialSchema {
     /**
-     * Table and column names as specified by Geopackage. This is the same 
thing as {@link #SQL_MM}
+     * Table and column names as specified by GeoPackage. This is the same 
thing as {@link #SQL_MM}
      * except for table names, for the case (Geopackage uses lower case) and 
for the addition of a
      * {@code geometry_type_name} column.
      */
     GEOPACKAGE(
+            "GeoPackage",                           // Human-readable name of 
this spaial schema.
             "gpkg_spatial_ref_sys",                 // Table for Spatial 
Reference System definitions.
             "srs_name",                             // Column for CRS names.
             "srs_id",                               // Column for CRS 
identifiers.
@@ -74,7 +75,8 @@ public enum SpatialSchema {
      * In Geopackage, this table is named {@code "gpkg_spatial_ref_sys"} but 
otherwise has identical content
      * except for the case (Geopackage uses lower case).
      */
-    SQL_MM("ST_SPATIAL_REFERENCE_SYSTEMS",          // Table for Spatial 
Reference System definitions.
+    SQL_MM("ISO-13249 SQL/MM",                      // Human-readable name of 
this spaial schema.
+           "ST_SPATIAL_REFERENCE_SYSTEMS",          // Table for Spatial 
Reference System definitions.
            "SRS_NAME",                              // Column for CRS names.
            "SRS_ID",                                // Column for CRS 
identifiers.
            "ORGANIZATION",                          // Column for CRS 
authority names.
@@ -104,6 +106,7 @@ public enum SpatialSchema {
      * }
      */
     SIMPLE_FEATURE(
+            "ISO 19125 / OGC Simple feature",       // Human-readable name of 
this spaial schema.
             "SPATIAL_REF_SYS",                      // Table for Spatial 
Reference System definitions.
             null,                                   // Column for CRS names, 
or `null` if none.
             "SRID",                                 // Column for CRS 
identifiers.
@@ -119,6 +122,11 @@ public enum SpatialSchema {
             "GEOMETRY_TYPE",                        // Column where the type 
of each geometry column is stored.
             GeometryTypeEncoding.NUMERIC);          // How geometry types are 
encoded in the above-cited type column.
 
+    /**
+     * The name of this spatial schema.
+     */
+    final String name;
+
     /**
      * Name of the table for Spatial Reference System definitions.
      * Example: {@code "SPATIAL_REF_SYS"}, {@code 
"ST_SPATIAL_REFERENCE_SYSTEMS"}.
@@ -205,6 +213,7 @@ public enum SpatialSchema {
     /**
      * Creates a new enumeration value.
      *
+     * @param name                    name of this spatial schema.
      * @param crsTable                name of the table for Spatial Reference 
System definitions.
      * @param crsNameColumn           name of the column for CRS names, or 
{@code null} if none.
      * @param crsIdentifierColumn     name of the column for CRS identifiers.
@@ -220,13 +229,14 @@ public enum SpatialSchema {
      * @param geomTypeColumn          name of the column where the type of 
each geometry column is stored, or null if none.
      * @param typeEncoding            how geometry types are encoded in the 
{@link #geomTypeColumn}.
      */
-    private SpatialSchema(String crsTable, String crsNameColumn, String 
crsIdentifierColumn,
+    private SpatialSchema(String name, String crsTable, String crsNameColumn, 
String crsIdentifierColumn,
                           String crsAuthorityNameColumn, String 
crsAuthorityCodeColumn,
                           Map<CRSEncoding,String> crsDefinitionColumn, String 
crsDescriptionColumn,
                           String geometryColumns, String geomCatalogColumn, 
String geomSchemaColumn,
                           String geomTableColumn, String geomColNameColumn, 
String geomTypeColumn,
                           GeometryTypeEncoding typeEncoding)
     {
+        this.name                   = name;
         this.crsTable               = crsTable;
         this.crsNameColumn          = crsNameColumn;
         this.crsIdentifierColumn    = crsIdentifierColumn;
diff --git 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/Postgres.java
 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/Postgres.java
index 356cf07238..b5a6ab50ea 100644
--- 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/Postgres.java
+++ 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/Postgres.java
@@ -42,6 +42,7 @@ import org.apache.sis.storage.sql.feature.SpatialSchema;
 import org.apache.sis.metadata.sql.privy.Dialect;
 import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.util.Version;
+import org.apache.sis.util.resources.Vocabulary;
 
 
 /**
@@ -120,6 +121,20 @@ public final class Postgres<G> extends Database<G> {
         return null;
     }
 
+    /**
+     * Completes the given database version with information about PostGIS 
version.
+     *
+     * @param  version  name and version of the database.
+     * @return given text, completed with PostGIS information if present.
+     */
+    @Override
+    protected CharSequence completeDatabaseVersion(CharSequence version) {
+        if (version != null) {
+            return Vocabulary.formatInternational(Vocabulary.Keys.With_2, 
version, "PostGIS " + postgisVersion);
+        }
+        return version;
+    }
+
     /**
      * Returns a function for getting values from a column having the given 
definition.
      * The given definition should include data SQL type and type name.
diff --git 
a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/PostgresTest.java
 
b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/PostgresTest.java
index 05418638e6..2babdb9146 100644
--- 
a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/PostgresTest.java
+++ 
b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/PostgresTest.java
@@ -17,6 +17,7 @@
 package org.apache.sis.storage.sql.postgis;
 
 import java.util.List;
+import java.util.Arrays;
 import java.util.stream.Stream;
 import java.util.function.Supplier;
 import java.sql.Connection;
@@ -30,6 +31,9 @@ import java.lang.reflect.Method;
 import org.locationtech.jts.geom.Point;
 import org.locationtech.jts.geom.Geometry;
 import org.opengis.geometry.Envelope;
+import org.opengis.metadata.Metadata;
+import org.opengis.metadata.identification.Identification;
+import org.opengis.metadata.spatial.SpatialRepresentationType;
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.crs.ProjectedCRS;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
@@ -55,6 +59,7 @@ import org.junit.jupiter.api.parallel.ResourceLock;
 import org.apache.sis.storage.sql.SQLStoreTest;
 import org.apache.sis.storage.sql.feature.GeometryGetterTest;
 import org.apache.sis.test.TestCase;
+import org.apache.sis.test.TestUtilities;
 import org.apache.sis.metadata.sql.TestDatabase;
 import org.apache.sis.referencing.crs.HardCodedCRS;
 
@@ -99,6 +104,19 @@ public final class PostgresTest extends TestCase {
         assertNull  (   version.getRevision());
     }
 
+    /**
+     * Performs some verification of store metadata.
+     *
+     * @param  metadata  the metadata to verify.
+     */
+    private static void validate(final Metadata metadata) {
+        final Identification identification = 
TestUtilities.getSingleton(metadata.getIdentificationInfo());
+        assertTrue(identification.getSpatialRepresentationTypes().containsAll(
+                Arrays.asList(SpatialRepresentationType.TEXT_TABLE,
+                              SpatialRepresentationType.VECTOR,
+                              SpatialRepresentationType.GRID)));
+    }
+
     /**
      * Tests reading and writing features and rasters.
      *
@@ -109,17 +127,18 @@ public final class PostgresTest extends TestCase {
     public void testSpatialFeatures() throws Exception {
         try (TestDatabase database = 
TestDatabase.createOnPostgreSQL(SQLStoreTest.SCHEMA, true)) {
             database.executeSQL(List.of(resource("SpatialFeatures.sql")));
-            final StorageConnector connector = new 
StorageConnector(database.source);
+            final var connector = new StorageConnector(database.source);
             connector.setOption(OptionKey.GEOMETRY_LIBRARY, 
GeometryLibrary.JTS);
             final ResourceDefinition table = ResourceDefinition.table(null, 
SQLStoreTest.SCHEMA, "SpatialData");
             try (SimpleFeatureStore store = new SimpleFeatureStore(new 
SQLStoreProvider(), connector, table)) {
+                validate(store.getMetadata());
                 /*
                  * Invoke the private `model()` method. We have to use 
reflection because the class
                  * is not in the same package and we do not want to expose the 
method in public API.
                  */
                 final Method modelAccessor = 
SQLStore.class.getDeclaredMethod("model");
                 modelAccessor.setAccessible(true);
-                final Postgres<?> pg = (Postgres<?>) 
modelAccessor.invoke(store);
+                final var pg = (Postgres<?>) modelAccessor.invoke(store);
                 try (Connection connection = database.source.getConnection();
                      ExtendedInfo info = new ExtendedInfo(pg, connection))
                 {
@@ -181,7 +200,7 @@ public final class PostgresTest extends TestCase {
             final ResultSet r = stmt.executeQuery();
             assertTrue(r.next());
             final ReadableByteChannel channel = 
Channels.newChannel(encoding.decode(r.getBinaryStream(1)));
-            final ChannelDataInput input = new ChannelDataInput(test.filename, 
channel, ByteBuffer.allocate(50), false);
+            final var input = new ChannelDataInput(test.filename, channel, 
ByteBuffer.allocate(50), false);
             RasterReaderTest.compareReadResult(test, reader, input);
             assertFalse(r.next());
         }
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java
index f4ed3b4507..41a6938b8d 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java
@@ -91,7 +91,6 @@ import org.apache.sis.metadata.sql.MetadataSource;
 import org.apache.sis.metadata.privy.Merger;
 import org.apache.sis.temporal.TimeMethods;
 import org.apache.sis.referencing.NamedIdentifier;
-import org.apache.sis.referencing.ImmutableIdentifier;
 import org.apache.sis.referencing.privy.AxisDirections;
 import org.apache.sis.geometry.AbstractEnvelope;
 import org.apache.sis.storage.AbstractResource;
@@ -1359,6 +1358,24 @@ public class MetadataBuilder {
         }
     }
 
+    /**
+     * Adds a mode in which the resource is represented.
+     * Storage location is:
+     *
+     * <ul>
+     *   <li>{@code metadata/identificationInfo/citation/presentationForm}</li>
+     * </ul>
+     *
+     * @param  form  mode in which the resource is represented, or {@code 
null} if none.
+     *
+     * @see #addSpatialRepresentation(SpatialRepresentationType)
+     */
+    public final void addPresentationForm(final PresentationForm form) {
+        if (form != null) {
+            citation().getPresentationForms().add(form);
+        }
+    }
+
     /**
      * Adds a brief narrative summary of the resource(s).
      * If a summary already exists, the new one will be appended after a new 
line.
@@ -1413,6 +1430,8 @@ public class MetadataBuilder {
      * </ul>
      *
      * @param  details  other details, or {@code null} for no-operation.
+     *
+     * @see #addFormatCitationDetails(CharSequence)
      */
     public final void addOtherCitationDetails(final CharSequence details) {
         final InternationalString i18n = trim(details);
@@ -1898,6 +1917,8 @@ public class MetadataBuilder {
      * </ul>
      *
      * @param  type  method used to spatially represent geographic 
information, or {@code null} for no-operation.
+     *
+     * @see #addPresentationForm(PresentationForm)
      */
     public final void addSpatialRepresentation(final SpatialRepresentationType 
type) {
         if (type != null) {
@@ -3041,7 +3062,7 @@ public class MetadataBuilder {
      * used as an alternative name of the current formaT. Storage location is:
      *
      * <ul>
-     *   <li>{@code 
metadata/identificationInfo/resourceFormat/formatSpecificationCitation/alternateTitle}</li>
+     *   <li>{@code 
metadata/identificationInfo/resourceFormat/formatSpecificationCitation/[alternate]Title}</li>
      * </ul>
      *
      * If this method is used together with {@link #setPredefinedFormat 
setPredefinedFormat(…)},
@@ -3053,6 +3074,16 @@ public class MetadataBuilder {
      * @see #addCompression(CharSequence)
      */
     public final void addFormatName(final CharSequence value) {
+        addFormatName(value, true);
+    }
+
+    /**
+     * Adds a name to the resource format, optionally doing nothing if a name 
is already present.
+     *
+     * @param  value  the format name, or {@code null} for no-operation.
+     * @param  more   {@code false} for doing nothing if a name is already 
present.
+     */
+    private void addFormatName(final CharSequence value, final boolean more) {
         final InternationalString i18n = trim(value);
         if (i18n != null) {
             @SuppressWarnings("LocalVariableHidesMemberVariable")
@@ -3060,7 +3091,7 @@ public class MetadataBuilder {
             DefaultCitation c = 
DefaultCitation.castOrCopy(format.getFormatSpecificationCitation());
             if (c == null) {
                 c = new DefaultCitation(i18n);
-            } else {
+            } else if (more) {
                 addIfNotPresent(c.getAlternateTitles(), i18n);
             }
             format.setFormatSpecificationCitation(c);
@@ -3103,49 +3134,63 @@ public class MetadataBuilder {
     }
 
     /**
-     * Adds a note about which reader is used. This method should not be 
invoked before
-     * the {@linkplain #addFormatName format name} has been set. Storage 
location is:
+     * Adds other information about the format. Apache SIS currently uses this
+     * location for specifying which software was used for reading the data.
+     * Storage location is:
      *
      * <ul>
-     *   <li>{@code 
metadata/identificationInfo/resourceFormat/formatSpecificationCitation/identifier}</li>
      *   <li>{@code 
metadata/identificationInfo/resourceFormat/formatSpecificationCitation/otherCitationDetails}</li>
      * </ul>
      *
+     * @param  details  other details about the format, or {@code null} for 
no-operation.
+     *
+     * @see #addOtherCitationDetails(CharSequence)
+     */
+    public final void addFormatCitationDetails(final CharSequence details) {
+        final InternationalString i18n = trim(details);
+        if (i18n != null) {
+            addIfNotPresent(getFormatCitation().getOtherCitationDetails(), 
i18n);
+        }
+    }
+
+    /**
+     * Adds a note about which reader is used. Apache SIS currently stores 
this information as other format
+     * citation details, but this location may change in a SIS future version 
if we find a better location.
+     * Storage location is as below, wrapped in a "Read by {0} version {1}" 
text:
+     *
+     * <ul>
+     *   <li>{@code 
metadata/identificationInfo/resourceFormat/formatSpecificationCitation/otherCitationDetails}</li>
+     * </ul>
+     *
+     * This method should not be invoked before the {@linkplain #addFormatName 
format name} has been set.
      * If this method is used together with {@link #setPredefinedFormat 
setPredefinedFormat(…)},
      * then the predefined format should be set <strong>before</strong> this 
method.
      *
-     * @param driver   library-specific way to identify the format (mandatory).
+     * <h4>Usage</h4>
+     * This method is invoked for various libraries such as GDAL, UCAR NetCDF 
and Apache SIS.
+     * For the specific case of Apache SIS, {@link 
#addFormatReaderSIS(String)} is a shortcut.
+     *
+     * @param driver   driver name to be wrapped in a "Read by {0} version 
{1}" text, or {@code null} for no-operation.
      * @param version  the library version, or {@code null} if unknown.
      */
-    public final void addFormatReader(final Identifier driver, final Version 
version) {
-        CharSequence title = null;
-        Citation authority = driver.getAuthority();
-        if (authority != null) {
-            title = authority.getTitle();
-            if (title != null) {
-                for (CharSequence t : authority.getAlternateTitles()) {
-                    if (t.length() < title.length()) {
-                        title = t;      // Alternate titles are often 
abbreviations.
-                    }
-                }
-            }
+    public final void addFormatReader(final CharSequence driver, final Version 
version) {
+        if (driver != null) {
+            
addFormatCitationDetails(Resources.formatInternational(Resources.Keys.ReadBy_2, 
driver,
+                    (version != null) ? version : 
Vocabulary.formatInternational(Vocabulary.Keys.Unspecified)));
         }
-        final DefaultCitation c = getFormatCitation();
-        addIfNotPresent(c.getOtherCitationDetails(),
-                Resources.formatInternational(Resources.Keys.ReadBy_2, (title 
!= null) ? title : driver.getCodeSpace(),
-                        (version != null) ? version : 
Vocabulary.formatInternational(Vocabulary.Keys.Unspecified)));
     }
 
     /**
      * Adds a note saying that Apache <abbr>SIS</abbr> has been used for 
decoding the format.
-     * This method should not be invoked before the {@linkplain #addFormatName 
format name} has been set.
+     * The argument should be the short name (identifier) of the {@code 
DataStoreProvider}.
+     * It is currently used only as a fallback when the {@linkplain 
#addFormatName format name}
+     * has not been set.
      *
-     * @param  name  the format name, or {@code null} if unspecified.
+     * @param  provider  the provider name, or {@code null} if unknown.
      */
-    public void addFormatReaderSIS(final String name) {
-        if (name != null) {
-            addFormatReader(new ImmutableIdentifier(Citations.SIS, 
Constants.SIS, name), Version.SIS);
-        }
+    public void addFormatReaderSIS(final String provider) {
+        addFormatName(provider, false);
+        addFormatReader(Citations.SIS.getTitle(), Version.SIS);
     }
 
     /**
diff --git 
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
index 9f9668999a..b7a34724eb 100644
--- 
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
@@ -624,7 +624,7 @@ final class Store extends URIDataStore implements 
FeatureSet {
     @Override
     public synchronized Metadata getMetadata() throws DataStoreException {
         if (metadata == null) {
-            final MetadataBuilder builder = new MetadataBuilder();
+            final var builder = new MetadataBuilder();
             final String format = (timeEncoding != null) && hasTrajectories ? 
StoreProvider.MOVING : StoreProvider.NAME;
             builder.setPredefinedFormat(format, listeners, true);
             builder.addFormatReaderSIS(format);
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/image/WorldFileStore.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/image/WorldFileStore.java
index 957e45bb34..e58db7265f 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/image/WorldFileStore.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/image/WorldFileStore.java
@@ -523,7 +523,7 @@ loop:   for (int convention=0;; convention++) {
     @Override
     public synchronized Metadata getMetadata() throws DataStoreException {
         if (metadata == null) try {
-            final MetadataBuilder builder = new MetadataBuilder();
+            final var builder = new MetadataBuilder();
             String format = reader().getFormatName();
             for (final String key : KNOWN_FORMATS) {
                 if (key.equalsIgnoreCase(format)) {
@@ -533,7 +533,7 @@ loop:   for (int convention=0;; convention++) {
                     break;
                 }
             }
-            builder.addFormatName(format);                          // Does 
nothing if `format` is null.
+            builder.addFormatName(format);      // Does nothing if `format` is 
null.
             builder.addFormatReaderSIS(WorldFileStoreProvider.NAME);
             builder.addResourceScope(ScopeCode.COVERAGE, null);
             builder.addSpatialRepresentation(null, 
getGridGeometry(MAIN_IMAGE), true);
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.java
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.java
index 4887863f70..9964ee5081 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.java
@@ -1414,6 +1414,11 @@ public class Vocabulary extends IndexedResourceBundle {
          */
         public static final short Width = 224;
 
+        /**
+         * {0} with {1}.
+         */
+        public static final short With_2 = 277;
+
         /**
          * World
          */
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.properties
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.properties
index 4bfda2c7a8..5c17e977e3 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.properties
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.properties
@@ -287,6 +287,7 @@ Visual                  = Visual
 Warnings                = Warnings
 WestBound               = West bound
 Width                   = Width
+With_2                  = {0} with {1}.
 World                   = World
 Write                   = Write
 Yellow                  = Yellow
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary_fr.properties
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary_fr.properties
index 18ca8ac441..e7d786b763 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary_fr.properties
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary_fr.properties
@@ -294,6 +294,7 @@ Visual                  = Visuel
 Warnings                = Avertissements
 WestBound               = Limite ouest
 Width                   = Largeur
+With_2                  = {0} avec {1}.
 World                   = Monde
 Write                   = \u00c9criture
 Yellow                  = Jaune
diff --git 
a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDALStore.java
 
b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDALStore.java
index 98f58e15c4..d1622c988d 100644
--- 
a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDALStore.java
+++ 
b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDALStore.java
@@ -38,7 +38,6 @@ import org.apache.sis.io.wkt.WKTFormat;
 import org.apache.sis.io.wkt.Convention;
 import org.apache.sis.io.stream.IOUtilities;
 import org.apache.sis.metadata.iso.citation.Citations;
-import org.apache.sis.referencing.ImmutableIdentifier;
 import org.apache.sis.setup.GeometryLibrary;
 import org.apache.sis.setup.OptionKey;
 import org.apache.sis.storage.AbstractResource;
@@ -361,8 +360,7 @@ public class GDALStore extends DataStore implements 
Aggregate {
             builder.addFormatName(driver.getName());
             String id = driver.getIdentifier();
             if (id != null) {
-                builder.addFormatReader(new 
ImmutableIdentifier(Citations.GDAL, Constants.GDAL, id),
-                                        
getProvider().getVersion().orElse(null));
+                builder.addFormatReader(Citations.GDAL.getTitle(), 
getProvider().getVersion().orElse(null));
             }
         }
     }


Reply via email to