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 f736d4ebd7b8543058b11712994d72a30765e067 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Mon Sep 16 17:00:23 2024 +0200 `GDALStoreProvider.probeContent(…)` should identify the driver without opening the dataset. --- .../main/org/apache/sis/storage/gdal/GDAL.java | 12 +- .../org/apache/sis/storage/gdal/GDALStore.java | 24 ++-- .../apache/sis/storage/gdal/GDALStoreProvider.java | 13 +- .../main/org/apache/sis/storage/gdal/Opener.java | 143 +++++++++------------ 4 files changed, 89 insertions(+), 103 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 aff1f1b0b3..1f7e6de681 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 @@ -87,6 +87,12 @@ final class GDAL extends NativeFunctions { */ final MethodHandle getMetadataItem; + /** + * <abbr>GDAL</abbr> {@code GDALDriverH GDALIdentifyDriver(const char *pszFilename, CSLConstList papszFileList)}. + * Identify the driver that can open a dataset. + */ + final MethodHandle identifyDriver; + /** * <abbr>GDAL</abbr> {@code GDALDatasetH GDALOpenEx(const char *pszFilename, …)}. * Opens a raster or vector file by invoking the open method of each driver in turn. @@ -275,6 +281,7 @@ 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, @@ -528,10 +535,13 @@ final class GDAL extends NativeFunctions { * This way to encode arrays of strings is specific to <abbr>GDAL</abbr>. * * @param arena the arena to use for memory allocation. - * @param items the Java strings to copy. + * @param items the Java strings to copy, or {@code null}. * @return the {@code NULL}-terminated array of string. */ static MemorySegment toNullTerminatedStrings(final Arena arena, final String... items) { + if (items == null) { + return MemorySegment.NULL; + } final var layout = ValueLayout.ADDRESS; final MemorySegment array = arena.allocate(layout, items.length + 1); for (int i=0; i<items.length; i++) { 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 c51c689141..3e9382ba92 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 @@ -153,13 +153,17 @@ public class GDALStore extends DataStore implements Aggregate, ResourceOnFileSys namespace = factory.createNameSpace(factory.createLocalName(null, filename), null); } final String[] drivers; - final Opener opener; - drivers = connector.getOption(GDALStoreProvider.DRIVERS_OPTION_KEY); - path = connector.getStorageAs(Path.class); - location = connector.commit(URI.class, GDALStoreProvider.NAME); - opener = Opener.read(provider, Opener.toURL(location, path), drivers); - closer = Cleaners.SHARED.register(this, opener); // Must do now in case of exception before completion. - handle = opener.handle; + drivers = connector.getOption(GDALStoreProvider.DRIVERS_OPTION_KEY); + location = connector.getStorageAs(URI.class); + path = connector.getStorageAs(Path.class); + String url = connector.commit(String.class, GDALStoreProvider.NAME); + if (location != null) { + url = Opener.toURL(location, path); + } + Opener opener; + opener = new Opener(provider, url, drivers); + closer = Cleaners.SHARED.register(this, opener); // Must do now in case of exception before completion. + handle = opener.handle; } /** @@ -178,7 +182,7 @@ public class GDALStore extends DataStore implements Aggregate, ResourceOnFileSys path = parent.path; location = parent.location; factory = parent.factory; - opener = Opener.read(getProvider(), url, driver); + opener = new Opener(getProvider(), url, driver); closer = Cleaners.SHARED.register(this, opener); // Must do now in case of exception before completion. handle = opener.handle; } @@ -312,7 +316,7 @@ public class GDALStore extends DataStore implements Aggregate, ResourceOnFileSys if (subdatasets != null && !subdatasets.isEmpty()) { components = subdatasets; } else { - components = UnmodifiableArrayList.wrap(TiledResource.groupBySizeAndType(this, gdal, handle)); + components = UnmodifiableArrayList.wrap(TiledResource.groupBySizeAndType(this, gdal, handle())); } } finally { ErrorHandler.throwOnFailure(this, "components"); @@ -336,7 +340,7 @@ public class GDALStore extends DataStore implements Aggregate, ResourceOnFileSys final MemorySegment result; try (var arena = Arena.ofConfined()) { final MemorySegment domain = arena.allocateFrom("SUBDATASETS"); - result = (MemorySegment) gdal.getMetadata.invokeExact(handle, domain); + result = (MemorySegment) gdal.getMetadata.invokeExact(handle(), domain); } catch (Throwable e) { throw GDAL.propagate(e); } diff --git a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDALStoreProvider.java b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDALStoreProvider.java index d1f6c053cc..ff505bb631 100644 --- a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDALStoreProvider.java +++ b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDALStoreProvider.java @@ -16,7 +16,6 @@ */ package org.apache.sis.storage.gdal; -import java.net.URI; import java.util.List; import java.util.Optional; import java.util.logging.Logger; @@ -283,17 +282,7 @@ public class GDALStoreProvider extends DataStoreProvider { */ @Override public ProbeResult probeContent(final StorageConnector connector) throws DataStoreException { - final URI url = connector.getStorageAs(URI.class); - if (url != null) { - final GDAL gdal = tryGDAL("probeContent").orElse(null); - if (gdal != null) { - try (Opener p = Opener.read(this, Opener.toURL(url, connector.getStorageAs(Path.class)))) { - String mimeType = p.getMetadataItem(gdal, "DMD_MIMETYPE"); - return new ProbeResult(true, mimeType, null); - } - } - } - return ProbeResult.UNSUPPORTED_STORAGE; + return Opener.probeContent(this, connector); } /** diff --git a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/Opener.java b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/Opener.java index af3dc8e7cf..4a2539745f 100644 --- a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/Opener.java +++ b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/Opener.java @@ -21,9 +21,9 @@ import java.util.Locale; import java.net.URI; import java.nio.file.Path; import java.lang.foreign.Arena; -import java.lang.foreign.ValueLayout; import java.lang.foreign.MemorySegment; import org.apache.sis.util.privy.Constants; +import org.apache.sis.storage.ProbeResult; import org.apache.sis.storage.StorageConnector; import org.apache.sis.storage.DataStoreException; @@ -40,7 +40,7 @@ import org.apache.sis.storage.DataStoreException; * @author Quentin Bialota (Geomatys) * @author Martin Desruisseaux (Geomatys) */ -final class Opener implements Runnable, AutoCloseable { +final class Opener implements Runnable { /** * Schemes of URLs to open with <var>GDAL</var> Virtual File Systems backed by <abbr>CURL</abbr>. */ @@ -54,11 +54,23 @@ final class Opener implements Runnable, AutoCloseable { private final GDALStoreProvider owner; /** - * Pointer to the <abbr>GDAL</abbr> object in native memory. + * Pointer to the <abbr>GDAL</abbr> object in native memory, or {@code null} if the file couldn't be opened. * This is a {@code GDALDatasetH} in the C/C++ <abbr>API</abbr>. */ final MemorySegment handle; + /** + * Creates a new instance for read operations on the given file or <abbr>URL</abbr>. + * + * @param owner owner of the set of handles for invoking <abbr>GDAL</abbr> functions. + * @param url <abbr>URL</abbr> (<var>GDAL</var> syntax) of the data store to open. + * @param allowedDrivers short names (identifiers) of drivers that may be used, or {@code null} for any driver. + * @throws DataStoreException if <var>GDAL</var> cannot open the data set. + */ + Opener(final GDALStoreProvider owner, final String url, final String... allowedDrivers) throws DataStoreException { + this(owner, url, new OpenFlag[] {OpenFlag.RASTER, OpenFlag.SHARED}, allowedDrivers, null, null); + } + /** * Creates a new instance for the given file or <abbr>URL</abbr>. * The {@code options} argument is driver-specific, except for the @@ -72,7 +84,7 @@ final class Opener implements Runnable, AutoCloseable { * </table> * * @param owner owner of the set of handles for invoking <abbr>GDAL</abbr> functions. - * @param url <abbr>URL</abbr> for <var>GDAL</var> of the data store to open. + * @param url <abbr>URL</abbr> (<var>GDAL</var> syntax) of the data store to open. * @param openFlags open flags to give to <abbr>GDAL</abbr>. * @param allowedDrivers short names (identifiers) of drivers that may be used, or {@code null} for any driver. * @param driverOptions driver-dependent options, or {@code null} if none. @@ -92,29 +104,12 @@ final class Opener implements Runnable, AutoCloseable { handle = (MemorySegment) gdal.open.invokeExact( arena.allocateFrom(url), OpenFlag.mask(openFlags), - allocateStringArray(arena, allowedDrivers), - allocateStringArray(arena, driverOptions), - allocateStringArray(arena, siblingFiles)); + GDAL.toNullTerminatedStrings(arena, allowedDrivers), + GDAL.toNullTerminatedStrings(arena, driverOptions), + GDAL.toNullTerminatedStrings(arena, siblingFiles)); } catch (Throwable e) { throw GDAL.propagate(e); } - if (GDAL.isNull(handle)) { - throw new DataStoreException(); // TODO: get message from GDAL. - } - } - - /** - * Creates a new instance for read operations on the given file or <abbr>URL</abbr>. - * - * @param owner owner of the set of handles for invoking <abbr>GDAL</abbr> functions. - * @param url <abbr>URL</abbr> for <var>GDAL</var> of the data store to open. - * @param allowedDrivers short names (identifiers) of drivers that may be used, or {@code null} for any driver. - * @throws DataStoreException if <var>GDAL</var> cannot open the data set. - */ - static Opener read(final GDALStoreProvider owner, final String url, final String... allowedDrivers) - throws DataStoreException - { - return new Opener(owner, url, new OpenFlag[] {OpenFlag.RASTER, OpenFlag.SHARED}, allowedDrivers, null, null); } /** @@ -139,56 +134,42 @@ final class Opener implements Runnable, AutoCloseable { } /** - * Allocates memory for an array of strings. - * - * @param arena the arena to allocate memory from. - * @param values the array of strings to allocate memory for. - * @return the array of pointer, or {@link MemorySegment#NULL} if the array is {@code null} or empty. - */ - private static MemorySegment allocateStringArray(final Arena arena, final String[] values) { - if (values == null || values.length == 0) { - return MemorySegment.NULL; - } - final MemorySegment array = arena.allocate(ValueLayout.ADDRESS, values.length); - for (int i=0; i < values.length; i++) { - array.setAtIndex(ValueLayout.ADDRESS, i, arena.allocateFrom(values[i])); - } - return array; - } - - /** - * Returns a metadata item from the driver. + * Returns the MIME type if the given storage appears to be supported by this data store. * - * @param gdal set of handles for invoking <abbr>GDAL</abbr> functions. - * @param name the key for the metadata item to fetch. - * @return the metadata given by the driver, or {@code null} if unspecified. + * @param owner the provider which is probing the file. + * @param connector information about the storage (URL, stream, <i>etc</i>). + * @return a support status with the MIME type, or {@code null} if the given URL is unrecognized. * @throws DataStoreException if an error occurred while invoking a <abbr>GDAL</abbr> function. */ - final String getMetadataItem(final GDAL gdal, final String name) throws DataStoreException { - try (var arena = Arena.ofConfined()) { - final MemorySegment driver = (MemorySegment) gdal.getDatasetDriver.invokeExact(handle); - if (!GDAL.isNull(driver)) { - MemorySegment n = arena.allocateFrom(name); - MemorySegment r = (MemorySegment) gdal.getMetadataItem.invokeExact(driver, n, MemorySegment.NULL); - return GDAL.toString(r); + static ProbeResult probeContent(final GDALStoreProvider owner, final StorageConnector connector) + throws DataStoreException + { + String url; + final URI location = connector.getStorageAs(URI.class); + if (location != null) { + url = toURL(location, connector.getStorageAs(Path.class)); + } else { + url = connector.getStorageAs(String.class); + } + if (url != null) { + final GDAL gdal = owner.tryGDAL("probeContent").orElse(null); + if (gdal != null) { + try (var arena = Arena.ofConfined()) { + final var driver = (MemorySegment) gdal.identifyDriver.invokeExact( + arena.allocateFrom(url), MemorySegment.NULL); + if (!GDAL.isNull(driver)) { + MemorySegment mimeType = (MemorySegment) gdal.getMetadataItem.invokeExact( + driver, arena.allocateFrom("DMD_MIMETYPE"), MemorySegment.NULL); + return new ProbeResult(true, GDAL.toString(mimeType), null); + } + } catch (Throwable e) { + throw GDAL.propagate(e); + } finally { + ErrorHandler.throwOnFailure(null, "probeContent"); + } } - } catch (Throwable e) { - throw GDAL.propagate(e); - } finally { - ErrorHandler.throwOnFailure(null, "probeContent"); } - return null; - } - - /** - * Closes the <abbr>GDAL</abbr> data set. This method shall be invoked - * by {@link GDALStoreProvider#probeContent(StorageConnector)} only. - * {@link GDALStore} has its own close method. - */ - @Override - public void close() throws DataStoreException { - run(); - ErrorHandler.throwOnFailure(null, "probeContent"); + return ProbeResult.UNSUPPORTED_STORAGE; } /** @@ -198,16 +179,18 @@ final class Opener implements Runnable, AutoCloseable { */ @Override public void run() { - owner.tryGDAL("close").ifPresent((gdal) -> { - final int err; - try { - err = (int) gdal.close.invokeExact(handle); - } catch (Throwable e) { - throw GDAL.propagate(e); - } - if (err != 0) { - ErrorHandler.errorOccurred(err); - } - }); + if (handle != null) { + owner.tryGDAL("close").ifPresent((gdal) -> { + final int err; + try { + err = (int) gdal.close.invokeExact(handle); + } catch (Throwable e) { + throw GDAL.propagate(e); + } + if (err != 0) { + ErrorHandler.errorOccurred(err); + } + }); + } } }