You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2026/10/02 10:10:07 UTC
(sis) 02/03: When opening a folder as a DataStore, exclude auxiliary files from the list of resources.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
commit 8bdb0c9430847594b355f0d2dac42ad76f19adf9
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Wed Sep 30 16:53:27 2026 +0900
When opening a folder as a DataStore, exclude auxiliary files from the list of resources.
---
.../apache/sis/storage/esri/RawRasterStore.java | 2 +
.../main/org/apache/sis/storage/folder/Store.java | 139 +++++++++++++--------
2 files changed, 91 insertions(+), 50 deletions(-)
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RawRasterStore.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RawRasterStore.java
index a165413074..6bc1e08763 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RawRasterStore.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RawRasterStore.java
@@ -550,9 +550,11 @@ final class RawRasterStore extends RasterStore {
* @throws DataStoreException if an error occurred while closing this data store.
*/
@Override
+ @SuppressWarnings("ConvertToTryWithResources")
public void close() throws DataStoreException {
try {
listeners.close(); // Should never fail.
+ @SuppressWarnings("LocalVariableHidesMemberVariable")
final ChannelDataInput input = this.input;
if (input != null) try {
input.channel.close();
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/folder/Store.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/folder/Store.java
index 6d377432cd..d30871166d 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/folder/Store.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/folder/Store.java
@@ -18,7 +18,7 @@ package org.apache.sis.storage.folder;
import java.util.Map;
import java.util.List;
-import java.util.ArrayList;
+import java.util.LinkedHashMap;
import java.util.Collection;
import java.util.Optional;
import java.util.logging.Level;
@@ -109,14 +109,14 @@ class Store extends URIDataStore implements StoreResource, UnstructuredAggregate
*
* @see #getMetadata()
*/
- private transient Metadata metadata;
+ private Metadata metadata;
/**
* Resources in the folder given at construction time, created when first needed.
*
* @see #components()
*/
- transient Collection<Resource> components;
+ Collection<Resource> components;
/**
* The provider to use for probing the directory content, opening files and creating new files.
@@ -126,10 +126,10 @@ class Store extends URIDataStore implements StoreResource, UnstructuredAggregate
protected final DataStoreProvider componentProvider;
/**
- * {@code true} if {@link #sharedRepository(Path)} has already been invoked for {@link #locationAsPath}.
+ * {@code true} if {@link #sharedDirectoryReported()} has already been invoked for {@link #locationAsPath}.
* This is used for avoiding to report the same message many times.
*/
- private transient boolean sharedRepositoryReported;
+ private boolean sharedDirectoryReported;
/**
* A structured view of this aggregate, or {@code null} if not net computed.
@@ -137,7 +137,7 @@ class Store extends URIDataStore implements StoreResource, UnstructuredAggregate
*
* @see #getStructuredView()
*/
- private transient Resource structuredView;
+ private Resource structuredView;
/**
* Creates a new folder store from the given file, path or URI.
@@ -283,51 +283,50 @@ class Store extends URIDataStore implements StoreResource, UnstructuredAggregate
@SuppressWarnings("ReturnOfCollectionOrArrayField")
public synchronized Collection<Resource> components() throws DataStoreException {
if (components == null) {
- final var resources = new ArrayList<DataStore>();
+ final var resources = new LinkedHashMap<Path, DataStore>(); // May contain null values for auxiliary files.
final var nameFactory = DefaultNameFactory.provider();
+ DataStoreException closeErrors = null;
try (DirectoryStream<Path> stream = Files.newDirectoryStream(locationAsPath, this)) {
- for (final Path candidate : stream) {
+ for (final Path childPath : stream) {
+ final Path realPath = childPath.toRealPath();
+ if (resources.containsKey(realPath)) {
+ // Resource already added as an auxiliary file of another resource (for example, a ".prj" file).
+ continue;
+ }
/*
- * The candidate path may be a symbolic link to a file that we have previously read.
- * In such case, use the existing data store. A use case is a directory containing
- * hundred of GeoTIFF files all accompanied by ".prj" files having identical content.
- * (Note: those ".prj" files should be invisible since they should be identified as
- * GeoTIFF auxiliary files, but current Store implementation does not know that).
+ * The path of the child may be a symbolic link to a file that we have already read
+ * in another directory. In such case, use (actually share) the existing data store.
*/
- final Path real = candidate.toRealPath();
- DataStore next = children.get(real);
- if (next instanceof Store) {
- ((Store) next).sharedRepository(real); // Warn about directories only.
- }
- if (next == null) {
+ DataStore child = children.get(realPath);
+ if (child == null) {
/*
* The candidate file has never been read before. Try to read it now.
- * If the file format is unknown (UnsupportedStorageException), we will
- * check if we can open it as a child folder store before to skip it.
+ * If the file format is unknown (UnsupportedStorageException),
+ * try to open the path as a child folder store.
*/
- final StorageConnector connector = new StorageConnector(configuration, candidate);
+ final var connector = new StorageConnector(configuration, childPath);
connector.setOption(OptionKey.PARENT_LISTENERS, listeners);
connector.setOption(OptionKey.OPEN_OPTIONS, new StandardOpenOption[] {
StandardOpenOption.READ // Restrict to read-only mode.
});
try {
if (componentProvider == null) {
- next = DataStores.open(connector); // May throw UnsupportedStorageException.
+ child = DataStores.open(connector); // May throw UnsupportedStorageException.
} else if (componentProvider.probeContent(connector).isSupported()) {
- next = componentProvider.open(connector); // Open a file of specified format.
- } else if (Files.isDirectory(candidate)) {
- next = new Store(this, connector, nameFactory); // Open a sub-directory.
+ child = componentProvider.open(connector); // Open a file of specified format.
+ } else if (Files.isDirectory(childPath)) {
+ child = new Store(this, connector, nameFactory); // Open a sub-directory.
} else {
connector.closeAllExcept(null); // Not the format specified at construction time.
continue;
}
} catch (UnsupportedStorageException ex) {
- if (!Files.isDirectory(candidate)) {
+ if (!Files.isDirectory(childPath)) {
connector.closeAllExcept(null);
listeners.warning(Level.FINE, null, ex);
continue;
}
- next = new Store(this, connector, nameFactory);
+ child = new Store(this, connector, nameFactory);
} catch (DataStoreException ex) {
try {
connector.closeAllExcept(null);
@@ -336,20 +335,26 @@ class Store extends URIDataStore implements StoreResource, UnstructuredAggregate
}
throw ex;
}
- /*
- * At this point we got the data store. It could happen that a store for
- * the same file has been added concurrently, so we need to check again.
- */
- final DataStore existing = children.putIfAbsent(real, next);
- if (existing != null) {
- next.close();
- next = existing;
- if (next instanceof Store) {
- ((Store) next).sharedRepository(real); // Warn about directories only.
+ }
+ /*
+ * If the resource has auxiliary files and if we opened some of them as standalone resources,
+ * close and exclude the latter. The null value is for remembering to not open that file again.
+ */
+ resources.put(realPath, child);
+ for (final Path auxiliary : child.getFileSet().map(FileSet::getPaths).orElse(List.of())) {
+ if (!realPath.equals(auxiliary)) {
+ final DataStore existing = resources.put(auxiliary.toRealPath(), null);
+ if (existing != null && !children.containsKey(realPath)) try {
+ existing.close();
+ } catch (DataStoreException ex) {
+ if (closeErrors == null) {
+ closeErrors = ex;
+ } else {
+ closeErrors.addSuppressed(ex);
+ }
}
}
}
- resources.add(next);
}
} catch (DirectoryIteratorException | UncheckedIOException ex) {
// The cause is an IOException (no other type allowed).
@@ -359,7 +364,45 @@ class Store extends URIDataStore implements StoreResource, UnstructuredAggregate
} catch (BackingStoreException ex) {
throw ex.unwrapOrRethrow(DataStoreException.class);
}
- components = Containers.copyToImmutableList(resources, Resource.class);
+ /*
+ * Finished the iteration over all directory entries. Remove auxiliary files (null values).
+ * For non-null values, check if the store has already been opened in another directory.
+ * It may happen if the directory tree contains symbolic links.
+ */
+ for (var it = resources.entrySet().iterator(); it.hasNext();) {
+ final Map.Entry<Path, DataStore> entry = it.next();
+ final DataStore next = entry.getValue();
+ if (next == null) {
+ it.remove();
+ continue;
+ }
+ final Path realPath = entry.getKey();
+ final DataStore existing = children.putIfAbsent(realPath, next);
+ if (existing != null) {
+ entry.setValue(existing);
+ /*
+ * Logs a warning if the path is a directory which has already been read.
+ * It may happen if the directory tree contains symbolic links.
+ * The warning will be reported one time per directory.
+ */
+ if (existing instanceof Store && ((Store) existing).sharedDirectoryReported()) {
+ listeners.warning(message(Resources.Keys.SharedDirectory_1, realPath));
+ }
+ try {
+ next.close();
+ } catch (DataStoreException ex) {
+ if (closeErrors == null) {
+ closeErrors = ex;
+ } else {
+ closeErrors.addSuppressed(ex);
+ }
+ }
+ }
+ }
+ components = Containers.copyToImmutableList(resources.values(), Resource.class);
+ if (closeErrors != null) {
+ listeners.warning(Level.FINE, null, closeErrors);
+ }
}
return components; // Safe because unmodifiable list.
}
@@ -372,17 +415,13 @@ class Store extends URIDataStore implements StoreResource, UnstructuredAggregate
}
/**
- * Logs a warning about a file that could be read, but happen to be a directory that we have read previously.
- * We could add the existing {@link Aggregate} instance in the parent {@code Aggregate} that we are building,
- * but doing so may create a cycle. Current version logs a warning instead because users may not be prepared
- * to handle cycles. Note that we have no guarantee that a cycle really exists at this stage, only that it may
- * exist.
+ * Whether to logs a warning about a file that could be read,
+ * but happens to be a directory that we have read before.
*/
- private void sharedRepository(final Path candidate) {
- if (!sharedRepositoryReported) {
- sharedRepositoryReported = true;
- listeners.warning(message(Resources.Keys.SharedDirectory_1, candidate));
- }
+ private synchronized boolean sharedDirectoryReported() {
+ final boolean r = sharedDirectoryReported;
+ sharedDirectoryReported = true;
+ return r;
}
/**