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 3346ea4370 When `WorldFileStore` is closed, close also the stream 
wrapped by `ImageInputStream`.
3346ea4370 is described below

commit 3346ea437045e07774151438559eed21b92bd032
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Wed Jun 8 15:26:19 2022 +0200

    When `WorldFileStore` is closed, close also the stream wrapped by 
`ImageInputStream`.
---
 .../sis/internal/storage/image/WorldFileStore.java | 43 +++++++++++++++++++---
 .../org/apache/sis/storage/StorageConnector.java   | 14 +++++++
 .../storage/io/ChannelImageInputStreamTest.java    |  1 +
 3 files changed, 52 insertions(+), 6 deletions(-)

diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/WorldFileStore.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/WorldFileStore.java
index 70c73afc46..ccbc9473a3 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/WorldFileStore.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/WorldFileStore.java
@@ -21,6 +21,7 @@ import java.util.Collection;
 import java.util.Map;
 import java.util.HashMap;
 import java.util.logging.Level;
+import java.io.Closeable;
 import java.io.IOException;
 import java.io.EOFException;
 import java.io.FileNotFoundException;
@@ -168,6 +169,25 @@ public class WorldFileStore extends PRJDataStore {
      */
     private ImageReader reader;
 
+    /**
+     * The object to close when {@code WorldFileStore} is closed. It may be a 
different object than
+     * reader input or writer output, because some {@link 
ImageInputStream#close()} implementations
+     * in the standard Java {@link javax.imageio.stream} package do not close 
the underlying stream.
+     *
+     * <p>The type is {@link Closeable} instead of {@link AutoCloseable} 
because the former is idempotent:
+     * invoking {@link Closeable#close()} many times has no effect. By 
contrast {@link AutoCloseable} does
+     * not offer this guarantee. Because it is hard to know what {@link 
ImageInputStream#close()} will do,
+     * we need idempotent {@code toClose} for safety. Note that {@link 
ImageInputStream#close()} violates
+     * the idempotent contract of {@link Closeable#close()}, so an additional 
check will be necessary in
+     * our {@link #close()} implementation.</p>
+     *
+     * @see javax.imageio.stream.FileCacheImageInputStream#close()
+     * @see javax.imageio.stream.FileCacheImageOutputStream#close()
+     * @see javax.imageio.stream.MemoryCacheImageInputStream#close()
+     * @see javax.imageio.stream.MemoryCacheImageOutputStream#close()
+     */
+    private Closeable toClose;
+
     /**
      * Width and height of the main image.
      * The {@link #gridGeometry} is assumed valid only for images having this 
size.
@@ -242,6 +262,9 @@ public class WorldFileStore extends PRJDataStore {
         listeners.useReadOnlyEvents();
         identifiers = new HashMap<>();
         suffix = format.suffix;
+        if (format.storage instanceof Closeable) {
+            toClose = (Closeable) format.storage;
+        }
         if (readOnly || !format.openAsWriter) {
             reader = format.getOrCreateReader();
             if (reader == null) {
@@ -784,16 +807,24 @@ loop:   for (int convention=0;; convention++) {
     public synchronized void close() throws DataStoreException {
         listeners.close();                  // Should never fail.
         final ImageReader codec = reader;
+        final Closeable  stream = toClose;
         reader       = null;
+        toClose      = null;
         metadata     = null;
         components   = null;
         gridGeometry = null;
-        if (codec != null) try {
-            final Object input = codec.getInput();
-            codec.setInput(null);
-            codec.dispose();
-            if (input instanceof AutoCloseable) {
-                ((AutoCloseable) input).close();
+        try {
+            Object input = null;
+            if (codec != null) {
+                input = codec.getInput();
+                codec.setInput(null);
+                codec.dispose();
+                if (input instanceof AutoCloseable) {
+                    ((AutoCloseable) input).close();
+                }
+            }
+            if (stream != null && stream != input) {
+                stream.close();
             }
         } catch (Exception e) {
             throw new DataStoreException(e);
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java
index 1f074814cc..2f2fab45f3 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java
@@ -1036,6 +1036,10 @@ public class StorageConnector implements Serializable {
              * FileCacheImageInputStream and related classes, but we don't do 
that for now because this
              * code should never be executed for InputStream storage. Instead 
getChannelDataInput(true)
              * should have created a ChannelImageInputStream or 
ChannelDataInput.
+             *
+             * In Apache SIS, ImageInputStream is used only by WorldFileStore. 
That store has its own
+             * mechanism for closing the stream used by ImageInputStream. It 
gives an extra safety in
+             * case the above paragraph does not hold.
              */
         }
         return asDataInput;
@@ -1350,6 +1354,16 @@ public class StorageConnector implements Serializable {
             reset();
             asDataOutput = ImageIO.createImageOutputStream(storage);
             addView(DataOutput.class, asDataOutput, null, (byte) 
(CASCADE_ON_RESET | CASCADE_ON_CLOSE));
+            /*
+             * Note: Java Image I/O wrappers for Input/OutputStream do NOT 
close the underlying streams.
+             * This is a complication for us. We could mitigate the problem by 
subclassing the standard
+             * FileCacheImageOutputStream and related classes, but we don't do 
that for now. A possible
+             * future evolution would be to complete ChannelImageOutputStream 
implementation instead.
+             *
+             * In Apache SIS, ImageOutputStream is used only by 
WorldFileStore. That store has its own
+             * mechanism for closing the stream used by ImageOutputStream. So 
the problem described in
+             * above paragraph would hopefully not occur in practice.
+             */
         }
         return asDataOutput;
     }
diff --git 
a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/io/ChannelImageInputStreamTest.java
 
b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/io/ChannelImageInputStreamTest.java
index 10c4f54fc7..a62fc04916 100644
--- 
a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/io/ChannelImageInputStreamTest.java
+++ 
b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/io/ChannelImageInputStreamTest.java
@@ -69,6 +69,7 @@ public final strictfp class ChannelImageInputStreamTest 
extends ChannelDataTestC
                 Channels.newChannel(new ByteArrayInputStream(data)), buffer, 
false);
         testedStream.setByteOrder(byteOrder);
         transferRandomData(testedStream, data.length - ARRAY_MAX_LENGTH, 24);
+        testedStream.close();
     }
 
     /**

Reply via email to