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

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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new b43d5d4d36 Reimplement `getComponentFiles()` by fetching this 
information from GDAL instead of guessing.
b43d5d4d36 is described below

commit b43d5d4d36f6995c0275994546b1da4cb5360871
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Mon Sep 16 17:55:15 2024 +0200

    Reimplement `getComponentFiles()` by fetching this information from GDAL 
instead of guessing.
---
 .../main/org/apache/sis/storage/gdal/GDAL.java     | 23 ++++-
 .../org/apache/sis/storage/gdal/GDALStore.java     | 97 +++++++++-------------
 .../apache/sis/storage/gdal/SubdatasetList.java    |  9 +-
 .../org/apache/sis/storage/gdal/TiledResource.java |  1 +
 .../org/apache/sis/storage/gdal/GDALStoreTest.java |  6 ++
 5 files changed, 73 insertions(+), 63 deletions(-)

diff --git 
a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDAL.java
 
b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDAL.java
index 1f7e6de681..1857780d47 100644
--- 
a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDAL.java
+++ 
b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDAL.java
@@ -114,6 +114,19 @@ final class GDAL extends NativeFunctions {
      */
     final MethodHandle free;
 
+    /**
+     * <abbr>GDAL</abbr> {@code void CSLDestroy(char **papszStrList)}.
+     * Releases memory allocated by <abbr>GDAL</abbr> for a string list.
+     * It is safe to pass {@code NULL}.
+     */
+    final MethodHandle destroy;
+
+    /**
+     * <abbr>GDAL</abbr> {@code char **GDALGetFileList(GDALDatasetH)}.
+     * Fetch files forming dataset.
+     */
+    final MethodHandle getFileList;
+
     /**
      * <abbr>GDAL</abbr> {@code GDALDriverH 
GDALGetDatasetDriver(GDALDatasetH)}.
      * Fetches the driver to which a dataset relates.
@@ -270,7 +283,12 @@ final class GDAL extends NativeFunctions {
         final var acceptTwoPtrsReturnPointer = 
FunctionDescriptor.of(ValueLayout.ADDRESS,     ValueLayout.ADDRESS, 
ValueLayout.ADDRESS);
         final Linker linker = Linker.nativeLinker();
 
+        // Memory management
+        free    = lookup(linker, "VSIFree",    
FunctionDescriptor.ofVoid(ValueLayout.ADDRESS));
+        destroy = lookup(linker, "CSLDestroy", 
FunctionDescriptor.ofVoid(ValueLayout.ADDRESS));
+
         // For Driver and/or all major objects
+        identifyDriver  = lookup(linker, "GDALIdentifyDriver",     
acceptTwoPtrsReturnPointer);
         getName         = lookup(linker, "GDALGetDriverLongName",  
acceptPointerReturnPointer);
         getIdentifier   = lookup(linker, "GDALGetDriverShortName", 
acceptPointerReturnPointer);
         getMetadata     = lookup(linker, "GDALGetMetadata",        
acceptTwoPtrsReturnPointer);
@@ -281,8 +299,6 @@ final class GDAL extends NativeFunctions {
                 ValueLayout.ADDRESS));  // const char* domain
 
         // For Opener
-        identifyDriver = lookup(linker, "GDALIdentifyDriver", 
acceptTwoPtrsReturnPointer);
-        free  = lookup(linker, "VSIFree",    
FunctionDescriptor.ofVoid(ValueLayout.ADDRESS));
         close = lookup(linker, "GDALClose",  acceptPointerReturnInt);
         open  = lookup(linker, "GDALOpenEx", 
FunctionDescriptor.of(ValueLayout.ADDRESS,
                 ValueLayout.ADDRESS,        // const char *pszFilename
@@ -291,7 +307,8 @@ final class GDAL extends NativeFunctions {
                 ValueLayout.ADDRESS,        // const char *const 
*papszOpenOptions
                 ValueLayout.ADDRESS));      // const char *const 
*papszSiblingFiles
 
-        // For all data set
+        // For all data sets
+        getFileList      = lookup(linker, "GDALGetFileList",      
acceptPointerReturnPointer);
         getDatasetDriver = lookup(linker, "GDALGetDatasetDriver", 
acceptPointerReturnPointer);
         getSpatialRef    = lookup(linker, "GDALGetSpatialRef",    
acceptPointerReturnPointer);
         getGCPSpatialRef = lookup(linker, "GDALGetGCPSpatialRef", 
acceptPointerReturnPointer);
diff --git 
a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDALStore.java
 
b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDALStore.java
index 3e9382ba92..f5272fc1a7 100644
--- 
a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDALStore.java
+++ 
b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDALStore.java
@@ -17,16 +17,12 @@
 package org.apache.sis.storage.gdal;
 
 import java.util.List;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
 import java.net.URI;
-import java.io.IOException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
 import java.nio.file.Path;
 import java.lang.ref.Cleaner;
 import java.lang.foreign.Arena;
@@ -238,7 +234,7 @@ public class GDALStore extends DataStore implements 
Aggregate, ResourceOnFileSys
      * @return name of the <abbr>GDAL</abbr> driver used for opening the file.
      * @throws DataStoreException if the driver name cannot be fetched.
      */
-    final String getDriverName(final GDAL gdal) throws DataStoreException {
+    private String getDriverName(final GDAL gdal) throws DataStoreException {
         try {
             var result = (MemorySegment) 
gdal.getDatasetDriver.invokeExact(handle());
             if (!GDAL.isNull(result)) {     // Paranoiac check.
@@ -272,6 +268,44 @@ public class GDALStore extends DataStore implements 
Aggregate, ResourceOnFileSys
         return Optional.ofNullable(param);
     }
 
+    /**
+     * Returns an array of files believed to be part of this resource.
+     *
+     * @todo This method is often used for copying a resources from one 
location to another.
+     *       <abbr>GDAL</abbr> provides a {@code GDALCopyDatasetFiles} 
function for this purpose.
+     *       That function is not yet used by Apache <abbr>SIS</abbr>.
+     *
+     * @return files used by this resource, or an empty array if unknown.
+     * @throws DataStoreException if the list of files cannot be obtained.
+     */
+    @Override
+    public synchronized Path[] getComponentFiles() throws DataStoreException {
+        final GDAL gdal = getProvider().GDAL();
+        final List<String> files;
+        try {
+            final var list = (MemorySegment) 
gdal.getFileList.invokeExact(handle());
+            try {
+                files = GDAL.fromNullTerminatedStrings(list);
+            } finally {
+                gdal.destroy.invokeExact(list);
+            }
+        } catch (Throwable e) {
+            throw GDAL.propagate(e);
+        }
+        if (files == null || files.isEmpty()) {
+            return (path != null) ? new Path[] {path} : new Path[0];
+        }
+        final var paths = new Path[files.size()];
+        for (int i=0; i < paths.length; i++) {
+            var item = Path.of(files.get(i));
+            if (path != null) {
+                item = path.resolveSibling(item);
+            }
+            paths[i] = item;
+        }
+        return paths;
+    }
+
     /**
      * Returns an identifier for the root resource of this data store, or an 
empty value if none.
      *
@@ -348,63 +382,12 @@ public class GDALStore extends DataStore implements 
Aggregate, ResourceOnFileSys
         if (all != null) {
             final String driver = getDriverName(gdal);
             if (driver != null) {   // Should never be null.
-                return new SubdatasetList(gdal, this, all);
+                return new SubdatasetList(gdal, this, driver, all);
             }
         }
         return null;
     }
 
-    /**
-     * Gets the paths to files potentially used by this resource.
-     * This method returns the path to the main file opened by the 
<abbr>GDAL</abbr> driver,
-     * and applies heuristic rules for guessing which other paths may be part 
of the format.
-     * Heuristic rules are used because <abbr>GDAL</abbr> provides no 
<abbr>API</abbr> for
-     * listing which files are used.
-     *
-     * @return files used by this resource, or an empty array if unknown.
-     * @throws DataStoreException if an error on the file system prevent the 
creation of the list.
-     */
-    @Override
-    public Path[] getComponentFiles() throws DataStoreException {
-        @SuppressWarnings("LocalVariableHidesMemberVariable")
-        final Path path = this.path;
-        if (path == null) {
-            return new Path[0];
-        }
-        final DirectoryStream.Filter<Path> filter;
-        switch (getDriver().getIdentifier()) {
-            default: {
-                return new Path[] {path};
-            }
-            case "AIG": {       // Arc/Info Binary Grid
-                // Special case: we must take "metadata.xml" and all "*.adf" 
in the folder.
-                filter = (Path entry) -> 
("metadata.xml".equalsIgnoreCase(entry.getFileName().toString())
-                                          || 
"adf".equalsIgnoreCase(IOUtilities.extension(entry)));
-                break;
-            }
-            // TODO: list more case where we want the same default as GeoTIFF.
-            case "GTiff": {
-                // List all existing paths with the same file name but 
possibly with different suffixes.
-                final String filename = path.getFileName().toString();
-                final int s = filename.lastIndexOf('.');
-                final String base = (s >= 0) ? filename.substring(0, s+1) : 
filename;
-                filter = (Path entry) -> 
entry.getFileName().toString().startsWith(base);
-                break;
-            }
-        }
-        final var paths = new ArrayList<Path>();
-        paths.addFirst(path);
-        try (DirectoryStream<Path> stream = 
Files.newDirectoryStream(path.getParent(),
-                (entry) -> !entry.equals(path) && filter.accept(entry) && 
Files.isRegularFile(entry)))
-        {
-            stream.forEach(paths::add);
-        } catch (IOException ex) {
-            throw cannotExecute(ex);
-        }
-        // Ensure that the main path is first.
-        return paths.toArray(Path[]::new);
-    }
-
     /**
      * Returns the object to use for parsing and formatting <abbr>CRS</abbr> 
definitions from/to <abbr>GDAL</abbr>.
      * This object must be used in a block synchronized on {@code this}.
diff --git 
a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/SubdatasetList.java
 
b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/SubdatasetList.java
index 20378a05e0..826aa56453 100644
--- 
a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/SubdatasetList.java
+++ 
b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/SubdatasetList.java
@@ -66,11 +66,14 @@ final class SubdatasetList extends AbstractList<Subdataset> 
{
      *
      * @param  gdal      set of handles for invoking <abbr>GDAL</abbr> 
functions.
      * @param  parent    the data store which owns this list.
+     * @param  driver    name of the <abbr>GDAL</abbr> driver to use for 
opening the sub-datasets.
      * @param  metadata  the metadata items for the {@code "SUBDATASETS"} 
domain.
      */
-    SubdatasetList(final GDAL gdal, final GDALStore parent, final List<String> 
metadata) throws DataStoreException {
-        this.parent  = parent;
-        this.driver  = parent.getDriverName(gdal);
+    SubdatasetList(final GDAL gdal, final GDALStore parent, final String 
driver, final List<String> metadata)
+            throws DataStoreException
+    {
+        this.parent = parent;
+        this.driver = driver;
         /*
          * URLs of all sub-dataset, optionally associated to their 
descriptions.
          * Keys are metadata keys. Values at index 0 are the URLs. Values at 
index 1 are descriptions.
diff --git 
a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/TiledResource.java
 
b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/TiledResource.java
index a5ba1c4d6b..45e42ee8f6 100644
--- 
a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/TiledResource.java
+++ 
b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/TiledResource.java
@@ -322,6 +322,7 @@ final class TiledResource extends TiledGridResource {
                                 m.getAtIndex(layout, 0),
                                 m.getAtIndex(layout, 3));
                     }
+                    // TODO: if above is not available, we could fallback on 
`GDALGCPsToGeoTransform`.
                 }
                 /*
                  * The axis order used by GDAL is not the axis order in the 
CRS definition.
diff --git 
a/incubator/src/org.apache.sis.storage.gdal/test/org/apache/sis/storage/gdal/GDALStoreTest.java
 
b/incubator/src/org.apache.sis.storage.gdal/test/org/apache/sis/storage/gdal/GDALStoreTest.java
index 9353bdf4a5..d316abd1e4 100644
--- 
a/incubator/src/org.apache.sis.storage.gdal/test/org/apache/sis/storage/gdal/GDALStoreTest.java
+++ 
b/incubator/src/org.apache.sis.storage.gdal/test/org/apache/sis/storage/gdal/GDALStoreTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.storage.gdal;
 
+import java.nio.file.Path;
 import java.awt.image.DataBuffer;
 import java.awt.image.RenderedImage;
 import org.opengis.util.GenericName;
@@ -156,6 +157,11 @@ public final class GDALStoreTest {
                     assertEquals(5, data.getElem(2000));    // Check a random 
value.
                 }
             }
+
+            // Check the file components
+            final Path[] paths = store.getComponentFiles();
+            assertEquals(1, paths.length);
+            assertEquals("test.tiff", paths[0].getFileName().toString());
         }
         assertTrue(foundGrid);
         assertTrue(foundBand);

Reply via email to