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 66809d27ae Handle empty files as non-existent files when opening a
`DataStore` in write mode. This is needed because when requesting a temporary
file, an empty file is created.
66809d27ae is described below
commit 66809d27ae2cb743c3a5fc3fb872fc162649ae4b
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Thu Dec 5 23:38:27 2024 +0100
Handle empty files as non-existent files when opening a `DataStore` in
write mode.
This is needed because when requesting a temporary file, an empty file is
created.
---
.../org/apache/sis/io/stream/ChannelFactory.java | 46 ++++++++++++++++---
.../main/org/apache/sis/io/stream/IOUtilities.java | 16 +++++++
.../org/apache/sis/storage/ProbeProviderPair.java | 6 +--
.../main/org/apache/sis/storage/ProbeResult.java | 10 ++++-
.../org/apache/sis/storage/StorageConnector.java | 15 ++++---
.../sis/storage/base/URIDataStoreProvider.java | 8 ++--
.../main/org/apache/sis/pending/jdk/JDK20.java | 51 ++++++++++++++++++++++
.../apache/sis/gui/internal/io/FileAccessView.java | 2 +-
8 files changed, 135 insertions(+), 19 deletions(-)
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelFactory.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelFactory.java
index c24fe66a5c..3f9854f97c 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelFactory.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelFactory.java
@@ -43,6 +43,7 @@ import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
+import org.apache.sis.util.ArraysExt;
import org.apache.sis.util.logging.Logging;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.system.Modules;
@@ -241,7 +242,7 @@ public abstract class ChannelFactory {
* way less surprising for the user (closer to the object
he has specified).
*/
if (file.isFile()) {
- return new Fallback(file, e);
+ return new Fallback(file, options, e);
}
}
}
@@ -287,9 +288,13 @@ public abstract class ChannelFactory {
@Override public WritableByteChannel writable(String
filename, StoreListeners listeners) throws IOException {
return Files.newByteChannel(path, optionSet);
}
- @Override public boolean isCreateNew() {
- if (optionSet.contains(StandardOpenOption.CREATE_NEW))
return true;
- if (optionSet.contains(StandardOpenOption.CREATE))
return Files.notExists(path);
+ @Override public boolean isCreateNew() throws IOException {
+ if (optionSet.contains(StandardOpenOption.CREATE_NEW))
{
+ return true;
+ }
+ if (optionSet.contains(StandardOpenOption.CREATE)) {
+ return IOUtilities.isAbsentOrEmpty(path);
+ }
return false;
}
};
@@ -327,8 +332,9 @@ public abstract class ChannelFactory {
* this factory has been created with an option that allows file creation.
*
* @return whether opening a channel will create a new file.
+ * @throws if an error occurred while checking file existence of
attributes.
*/
- public boolean isCreateNew() {
+ public boolean isCreateNew() throws IOException {
return false;
}
@@ -532,6 +538,11 @@ public abstract class ChannelFactory {
*/
private final File file;
+ /**
+ * The {@code READ}, {@code CREATE} or {@code CREATE_NEW} option.
+ */
+ private final StandardOpenOption option;
+
/**
* The reason why we are using this fallback instead of a {@link Path}.
* Will be reported at most once, then set to {@code null}.
@@ -541,10 +552,19 @@ public abstract class ChannelFactory {
/**
* Creates a new fallback to use if the given file cannot be converted
to a {@link Path}.
*/
- Fallback(final File file, final InvalidPathException cause) {
+ Fallback(final File file, final OpenOption[] options, final
InvalidPathException cause) {
super(true);
this.file = file;
this.cause = cause;
+ @SuppressWarnings("LocalVariableHidesMemberVariable")
+ StandardOpenOption option = StandardOpenOption.CREATE_NEW;
+ if (!ArraysExt.contains(options, option)) {
+ option = StandardOpenOption.CREATE;
+ if (!ArraysExt.contains(options, option)) {
+ option = StandardOpenOption.READ; // Could actually be
WRITE, but we don't need to distinguish.
+ }
+ }
+ this.option = option;
}
/**
@@ -638,6 +658,20 @@ public abstract class ChannelFactory {
public WritableByteChannel writable(String filename, StoreListeners
listeners) throws IOException {
return outputStream(filename, listeners).getChannel();
}
+
+ /**
+ * Returns {@code true} if opening the channel will create a new,
initially empty, file.
+ */
+ @Override
+ public boolean isCreateNew() throws IOException {
+ switch (option) {
+ default: return false;
+ case CREATE_NEW: return true;
+ case CREATE: {
+ return !file.exists() || (file.isFile() && file.length()
== 0);
+ }
+ }
+ }
}
/**
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/IOUtilities.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/IOUtilities.java
index 3dd75cd7ed..02a71bcfe1 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/IOUtilities.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/IOUtilities.java
@@ -41,10 +41,12 @@ import java.nio.file.Path;
import java.nio.file.OpenOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.FileSystemNotFoundException;
+import java.nio.file.attribute.BasicFileAttributes;
import java.nio.charset.StandardCharsets;
import javax.imageio.stream.ImageInputStream;
import javax.xml.stream.Location;
import javax.xml.stream.XMLStreamReader;
+import org.apache.sis.pending.jdk.JDK20;
import org.apache.sis.util.CharSequences;
import org.apache.sis.util.Static;
import org.apache.sis.util.resources.Errors;
@@ -558,6 +560,20 @@ check: if (stream instanceof ChannelData) {
return false;
}
+ /**
+ * Returns {@code true} if the file at the specified path is absent or an
empty file.
+ * If the file exists but is not a regular file, then this method returns
{@code false}.
+ *
+ * @param path the path to test.
+ * @return whether the file is absent or empty.
+ * @throws IOException if an error occurred while fetching the attributes.
+ */
+ public static boolean isAbsentOrEmpty(final Path path) throws IOException {
+ final BasicFileAttributes attributes = JDK20.readAttributesIfExists(
+ path.getFileSystem().provider(), path,
BasicFileAttributes.class);
+ return (attributes == null) || (attributes.isRegularFile() &&
attributes.size() == 0);
+ }
+
/**
* Returns {@code true} if the given object is an output stream with no
read capability.
*
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/ProbeProviderPair.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/ProbeProviderPair.java
index acc9f44941..129854dd85 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/ProbeProviderPair.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/ProbeProviderPair.java
@@ -54,14 +54,14 @@ final class ProbeProviderPair {
/**
* Sets the {@linkplain #probe} result for a file that does not exist yet.
- * The result will be {@link ProbeResult#SUPPORTED} or {@code
UNSUPPORTED_STORAGE},
+ * The result will be {@link ProbeResult#CREATE_NEW} or {@code
UNSUPPORTED_STORAGE},
* depending on whether the {@linkplain #provider} supports the creation
of new storage.
* In both cases, {@link StorageConnector#wasProbingAbsentFile()} will
return {@code true}.
*
* <p>This method is invoked for example if the storage is a file, the
file does not exist
* but {@link StandardOpenOption#CREATE} or {@link
StandardOpenOption#CREATE_NEW CREATE_NEW}
* option was provided and the data store has write capability. Note
however that declaring
- * {@code SUPPORTED} is not a guarantee that the data store will
successfully create the resource.
+ * {@code CREATE_NEW} is not a guarantee that the data store will
successfully create the resource.
* For example we do not verify if the file system grants write permission
to the application.</p>
*
* @see StorageConnector#wasProbingAbsentFile()
@@ -69,7 +69,7 @@ final class ProbeProviderPair {
final void setProbingAbsentFile() {
final StoreMetadata md =
provider.getClass().getAnnotation(StoreMetadata.class);
if (md == null || ArraysExt.contains(md.capabilities(),
Capability.CREATE)) {
- probe = ProbeResult.SUPPORTED;
+ probe = ProbeResult.CREATE_NEW;
} else {
probe = ProbeResult.UNSUPPORTED_STORAGE;
}
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/ProbeResult.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/ProbeResult.java
index df18b15c6b..9151e91059 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/ProbeResult.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/ProbeResult.java
@@ -45,7 +45,7 @@ import org.apache.sis.util.privy.Strings;
* In such cases, SIS will revisit those providers only if no better suited
provider is found.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.4
+ * @version 1.5
*
* @see DataStoreProvider#probeContent(StorageConnector)
*
@@ -57,6 +57,14 @@ public class ProbeResult implements Serializable {
*/
private static final long serialVersionUID = -4977853847503500550L;
+ /**
+ * The {@code DataStoreProvider} will create a new file.
+ * The file does not exist yet or is empty.
+ *
+ * @since 1.5
+ */
+ public static final ProbeResult CREATE_NEW = new Constant(true,
"CREATE_NEW");
+
/**
* The {@code DataStoreProvider} recognizes the given storage, but has no
additional information.
* The {@link #isSupported()} method returns {@code true}, but the
{@linkplain #getMimeType() MIME type}
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/StorageConnector.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/StorageConnector.java
index 1b64ee68cf..b851d8b821 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/StorageConnector.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/StorageConnector.java
@@ -1038,14 +1038,19 @@ public class StorageConnector implements Serializable {
* This method may return {@code true} if all the following conditions are
true:
*
* <ul>
- * <li>A previous {@link #getStorageAs(Class)} call requested some kind
of input stream
- * (e.g. {@link InputStream}, {@link ImageInputStream}, {@link
DataInput}, {@link Reader}).</li>
- * <li>The {@linkplain #getStorage() storage} is an object convertible
to a {@link Path} and the
- * file identified by that path {@linkplain
java.nio.file.Files#notExists does not exist}.</li>
- * <li>The {@linkplain #getOption(OptionKey) optons} given to this
{@code StorageConnector} include
+ * <li>The {@linkplain #getOption(OptionKey) options} given to this
{@code StorageConnector} include
* {@link java.nio.file.StandardOpenOption#CREATE} or {@code
CREATE_NEW}.</li>
* <li>The {@code getStorageAs(…)} and {@code wasProbingAbsentFile()}
calls happened in the context of
* {@link DataStores} probing the storage content in order to choose
a {@link DataStoreProvider}.</li>
+ * <li>A previous {@link #getStorageAs(Class)} call requested some kind
of input stream
+ * (e.g. {@link InputStream}, {@link ImageInputStream}, {@link
DataInput}, {@link Reader}).</li>
+ * <li>One of the following conditions is true:
+ * <ul>
+ * <li>The input stream is empty.</li>
+ * <li>The {@linkplain #getStorage() storage} is an object
convertible to a {@link Path} and the
+ * file identified by that path {@linkplain
java.nio.file.Files#notExists does not exist}.</li>
+ * </ul>
+ * </li>
* </ul>
*
* If all above conditions are true, then {@link #getStorageAs(Class)}
returns {@code null} instead of creating
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/URIDataStoreProvider.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/URIDataStoreProvider.java
index 911ab593fc..565670359c 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/URIDataStoreProvider.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/URIDataStoreProvider.java
@@ -19,11 +19,11 @@ package org.apache.sis.storage.base;
import java.util.Optional;
import java.io.DataInput;
import java.io.DataOutput;
+import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.nio.file.Path;
-import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.StandardOpenOption;
import java.nio.charset.Charset;
@@ -221,9 +221,11 @@ public abstract class URIDataStoreProvider extends
DataStoreProvider {
if (ArraysExt.contains(options, StandardOpenOption.CREATE_NEW)) {
return IOUtilities.isKindOfPath(storage);
}
- if (ArraysExt.contains(options, StandardOpenOption.CREATE)) {
+ if (ArraysExt.contains(options, StandardOpenOption.CREATE)) try {
final Path path = connector.getStorageAs(Path.class);
- return (path != null) && Files.notExists(path);
+ return (path != null) && IOUtilities.isAbsentOrEmpty(path);
+ } catch (IOException e) {
+ throw new DataStoreException(e);
}
}
return false;
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/jdk/JDK20.java
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/jdk/JDK20.java
new file mode 100644
index 0000000000..2142cc573a
--- /dev/null
+++
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/jdk/JDK20.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.pending.jdk;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.spi.FileSystemProvider;
+import java.nio.file.attribute.BasicFileAttributes;
+
+
+/**
+ * Place holder for some functionalities defined in a JDK more recent than
Java 11.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ */
+public final class JDK20 {
+ /**
+ * Do not allow instantiation of this class.
+ */
+ private JDK20() {
+ }
+
+ /**
+ * Reads a file's attributes as a bulk operation if it exists.
+ */
+ public static <A extends BasicFileAttributes> A
readAttributesIfExists(FileSystemProvider provider,
+ Path path, Class<A> type, LinkOption... options) throws IOException
+ {
+ try {
+ return provider.readAttributes(path, type, options);
+ } catch (NoSuchFileException ignore) {
+ return null;
+ }
+ }
+}
diff --git
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/io/FileAccessView.java
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/io/FileAccessView.java
index 6e105a1105..f1a9571a8a 100644
---
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/io/FileAccessView.java
+++
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/io/FileAccessView.java
@@ -118,7 +118,7 @@ public final class FileAccessView extends Widget implements
UnaryOperator<Channe
* Returns {@code true} if opening the channel will create a new,
initially empty, file.
*/
@Override
- public boolean isCreateNew() {
+ public boolean isCreateNew() throws IOException {
return factory.isCreateNew();
}