private ProbeProviderPair lookup()

in endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/DataStoreRegistry.java [207:392]


    private ProbeProviderPair lookup(final Object storage, final Capability capability,
            Predicate<DataStoreProvider> preferred, final boolean open)
            throws DataStoreException
    {
        final boolean writable;
        StorageConnector connector;                 // Will be reset to `null` if it shall not be closed.
        if (storage instanceof StorageConnector) {
            connector = (StorageConnector) storage;
            writable = (capability == Capability.WRITE) && connector.getOption(OptionKey.OPEN_OPTIONS) == null;
            final var filter = connector.getOption(InternalOptionKey.PREFERRED_PROVIDERS);
            if (filter != null) {
                preferred = (preferred != null) ? preferred.and(filter) : filter;
            }
        } else {
            connector = new StorageConnector(storage);
            writable = (capability == Capability.WRITE);
        }
        /*
         * If this method is invoked by `DataStores.openWritable(…)`, add NIO open options if not already present.
         * Note that this code may modify a user provided storage connector. It should be okay considering that
         * each `StorageConnector` instance should be short-lived and used only once.
         */
        if (writable) {
            connector.setOption(OptionKey.OPEN_OPTIONS, new StandardOpenOption[] {
                StandardOpenOption.CREATE, StandardOpenOption.WRITE
            });
        }
        if (preferred != null) {
            connector.setOption(InternalOptionKey.PREFERRED_PROVIDERS, preferred);
        }
        /*
         * If we can get a filename extension from the given storage (file, URL, etc.), we may perform two times
         * more iterations on the provider list. One serie of iterations will use only the providers that declare
         * capability to read or write files of that suffix (Category.SUFFIX_MATCH). Only if no provider has been
         * able to read or write that file, we will do another iteration on other providers (Category.IGNORE_SUFFIX).
         * The intent is to avoid DataStoreProvider.probeContent(…) invocations loading large dependencies.
         */
        final String      extension   = connector.getFileExtension();
        final boolean     useSuffix   = !Strings.isNullOrEmpty(extension);
        final boolean     isWriteOnly = (capability == Capability.WRITE) && IOUtilities.isWriteOnly(connector.getStorage());
        ProbeProviderPair selected    = null;
        final var needMoreBytes = new LinkedList<ProbeProviderPair>();
        try {
            boolean isFirstIteration = true;
search:     for (final Category category : Category.values()) {
                if (category.preferred && (preferred == null)) continue;
                if (category.useSuffix && !useSuffix) continue;
                /*
                 * All usages of `loader` and its `providers` iterator must be protected in a synchronized block,
                 * because ServiceLoader is not thread-safe. We try to keep the synhronization block as small as
                 * possible for less contention. In particular, the `probeContent(connector)` method call may be
                 * costly.
                 */
                final Iterator<DataStoreProvider> providers;
                DataStoreProvider provider;
                synchronized (loader) {
                    providers = loader.iterator();
                    provider = providers.hasNext() ? providers.next() : null;
                }
                while (provider != null) {
                    /*
                     * Check if the provider should be tested in current iteration.
                     * The criteria for testing a provider is determined by comparing
                     * provider metadata with the category tested in current iteration.
                     */
                    boolean accept;
                    final StoreMetadata md = provider.getClass().getAnnotation(StoreMetadata.class);
                    if (md == null) {
                        accept = isFirstIteration;      // If no metadata, test only during one iteration.
                    } else {
                        accept = (category.preferred || md.yieldPriority() == category.yieldPriority) &&
                                 ArraysExt.contains(md.capabilities(), capability);
                        if (accept & useSuffix) {
                            accept = ArraysExt.containsIgnoreCase(md.fileSuffixes(), extension) == category.useSuffix;
                        }
                    }
                    if (accept & (preferred != null)) {
                        accept = (preferred.test(provider) == category.preferred);
                    }
                    /*
                     * At this point, it has been determined whether the provider should be tested in current iteration.
                     * If accepted, perform now the probing operation for checking if the current provider is suitable.
                     * The `connector.probing` field is set to a non-null value for telling `StorageConnector` to not
                     * create empty file if the file does not exist (it has no effect in read-only mode).
                     */
                    if (accept) {
                        final var candidate = new ProbeProviderPair(provider);
                        if (isWriteOnly) {
                            /*
                             * We cannot probe a write-only storage. Rely on the filtering done before this block,
                             * which was based on format name and file suffix, and use the first filtered provider.
                             */
                            selected = candidate;
                            break search;
                        }
                        final ProbeProviderPair old = connector.probing;
                        final ProbeResult probe;
                        try {
                            connector.probing = candidate;
                            probe = provider.probeContent(connector);
                        } finally {
                            connector.probing = old;
                        }
                        candidate.probe = probe;
                        if (probe.isSupported()) {
                            /*
                             * Stop at the first provider claiming to be able to read the storage.
                             * Do not iterate over the list of deferred providers (if any).
                             */
                            selected = candidate;
                            break search;
                        }
                        if (ProbeResult.INSUFFICIENT_BYTES.equals(probe)) {
                            /*
                             * If a provider doesn't have enough bytes for answering the question,
                             * try again after this loop with more bytes in the buffer, unless we
                             * found another provider.
                             */
                            needMoreBytes.add(candidate);
                        } else if (ProbeResult.UNDETERMINED.equals(probe)) {
                            /*
                             * If a provider doesn't know whether it can open the given storage,
                             * we will try it only if we find no provider retuning SUPPORTED.
                             * We select the first provider because it is more likely to be the
                             * one for the file extension of the given storage.
                             */
                            if (selected == null) {
                                selected = candidate;
                            }
                        }
                    }
                    synchronized (loader) {
                        provider = providers.hasNext() ? providers.next() : null;
                    }
                }
                /*
                 * If any provider did not had enough bytes for answering the `probeContent(…)` question,
                 * get more bytes and try again. We try to prefetch more bytes only if we have no choice
                 * in order to avoid latency on network connection.
                 */
                while (!needMoreBytes.isEmpty() && connector.prefetch()) {
                    for (final Iterator<ProbeProviderPair> it = needMoreBytes.iterator(); it.hasNext();) {
                        final ProbeProviderPair p = it.next();
                        p.probe = p.provider.probeContent(connector);
                        if (p.probe.isSupported()) {
                            selected = p;
                            break search;
                        }
                        if (!ProbeResult.INSUFFICIENT_BYTES.equals(p.probe)) {
                            if (selected == null && ProbeResult.UNDETERMINED.equals(p.probe)) {
                                selected = p;                   // To be used only if we don't find a better match.
                            }
                            it.remove();        // UNSUPPORTED_* or UNDETERMINED: do not try again those providers.
                        }
                    }
                }
                /*
                 * If we filtered providers by the file extension without finding a suitable provider,
                 * try again with all other providers (even if they are for another file extension).
                 * We do that by moving to the next `Category`.
                 */
                isFirstIteration = false;
            }
            /*
             * If a provider has been found, or if a provider returned UNDETERMINED, use that one
             * for opening a DataStore. Note that if more than one provider returned UNDETERMINED,
             * the selected one is arbitrary and may change in different execution. Implementers
             * shall avoid the UNDETERMINED value as much as possible (this value should be used
             * only for RAW image format).
             */
            if (open && selected != null) {
                selected.store = selected.provider.open(connector);
                connector = null;                                               // For preventing it to be closed.
            }
        } finally {
            if (connector != null && connector != storage) {
                connector.closeAllExcept(null);
            }
        }
        if (open && selected == null) {
            @SuppressWarnings("null")                       // `connector` is null only if `selected` is non-null.
            final String name = connector.getStorageName();
            throw new UnsupportedStorageException(null, Resources.Keys.UnknownFormatFor_1, name);
        }
        return selected;
    }