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 b12a8b466af74f71e787357a2adf40160706f195
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Mon Oct 30 12:21:13 2023 +0100

    Add `ImageOutputStream` support in `StorageConnector`.
---
 .../sis/io/stream/ChannelImageOutputStream.java    |  14 +++
 .../org/apache/sis/storage/StorageConnector.java   | 109 ++++++++++++++-------
 2 files changed, 90 insertions(+), 33 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelImageOutputStream.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelImageOutputStream.java
index 46f8afe93c..d6f9de7347 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelImageOutputStream.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelImageOutputStream.java
@@ -21,6 +21,7 @@ import java.io.OutputStream;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.channels.ByteChannel;
+import java.nio.channels.ReadableByteChannel;
 import javax.imageio.stream.IIOByteBuffer;
 import javax.imageio.stream.ImageOutputStream;
 
@@ -68,6 +69,19 @@ public class ChannelImageOutputStream extends OutputStream 
implements ImageOutpu
         output = new ChannelDataOutput(filename, channel, buffer);
     }
 
+    /**
+     * Creates a new input/output stream wrapping the given output data 
channel.
+     *
+     * @param  output  the object to use for writing to the channel.
+     * @throws ClassCastException if the output channel is not readable.
+     * @throws IOException if the stream cannot be created.
+     */
+    public ChannelImageOutputStream(final ChannelDataOutput output) throws 
IOException {
+        this.output = output;
+        input = new ChannelImageInputStream(output.filename, 
(ReadableByteChannel) output.channel, output.buffer, true);
+        writing = true;
+    }
+
     /**
      * Returns the {@linkplain #input} or {@linkplain #output},
      * depending on whether this stream is reading or writing.
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 814d664963..a2bf92f40a 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
@@ -67,6 +67,7 @@ import org.apache.sis.io.stream.ChannelData;
 import org.apache.sis.io.stream.ChannelDataInput;
 import org.apache.sis.io.stream.ChannelDataOutput;
 import org.apache.sis.io.stream.ChannelImageInputStream;
+import org.apache.sis.io.stream.ChannelImageOutputStream;
 import org.apache.sis.io.stream.InputStreamAdapter;
 import org.apache.sis.io.stream.RewindableLineReader;
 import org.apache.sis.io.stream.InternalOptionKey;
@@ -208,6 +209,7 @@ public class StorageConnector implements Serializable {
         add(DataInput.class,         StorageConnector::createDataInput);
         add(DataOutput.class,        StorageConnector::createDataOutput);
         add(ImageInputStream.class,  StorageConnector::createImageInputStream);
+        add(ImageOutputStream.class, 
StorageConnector::createImageOutputStream);
         add(InputStream.class,       StorageConnector::createInputStream);
         add(OutputStream.class,      StorageConnector::createOutputStream);
         add(Reader.class,            StorageConnector::createReader);
@@ -389,7 +391,6 @@ public class StorageConnector implements Serializable {
          * @param  cascade     bitwise combination of {@link 
#CASCADE_ON_CLOSE}, {@link #CASCADE_ON_RESET}
          *                     or {@link #CLEAR_ON_RESET}.
          */
-        @SuppressWarnings("ThisEscapedInObjectConstruction")
         Coupled(final Coupled wrapperFor, final byte cascade) {
             this.wrapperFor = wrapperFor;
             this.cascade    = cascade;
@@ -521,6 +522,8 @@ public class StorageConnector implements Serializable {
                  * which should cause the block below (with a call to 
`rewind()`) to be executed.
                  */
                 ((ChannelData) view).seek(0);
+            } else if (view instanceof ChannelImageOutputStream) {
+                ((ChannelImageOutputStream) view).output().seek(0);
             } else if (view instanceof Channel) {
                 /*
                  * Searches for a ChannelDataInput wrapping the channel, 
because it contains the original position
@@ -718,7 +721,6 @@ public class StorageConnector implements Serializable {
      *       <li>If the {@linkplain #getStorage() storage} object is an 
instance of the {@link Path},
      *           {@link File}, {@link URL}, {@link URI} or {@link 
CharSequence} types,
      *           returns the string representation of their path.</li>
-     *
      *       <li>Otherwise this method returns {@code null}.</li>
      *     </ul>
      *   </li>
@@ -727,7 +729,6 @@ public class StorageConnector implements Serializable {
      *       <li>If the {@linkplain #getStorage() storage} object is an 
instance of the {@link Path},
      *           {@link File}, {@link URL}, {@link URI} or {@link 
CharSequence} types and
      *           that type can be converted to the requested type, returned 
the conversion result.</li>
-     *
      *       <li>Otherwise this method returns {@code null}.</li>
      *     </ul>
      *   </li>
@@ -735,35 +736,29 @@ public class StorageConnector implements Serializable {
      *     <ul>
      *       <li>If the {@linkplain #getStorage() storage} object can be 
obtained as described in bullet 2 of the
      *           {@code DataInput} section below, then this method returns the 
associated byte buffer.</li>
-     *
      *       <li>Otherwise this method returns {@code null}.</li>
      *     </ul>
      *   </li>
      *   <li>{@link DataInput}:
      *     <ul>
      *       <li>If the {@linkplain #getStorage() storage} object is already 
an instance of {@code DataInput}
-     *           (including the {@link ImageInputStream} and {@link 
javax.imageio.stream.ImageOutputStream} types),
+     *           (including the {@link ImageInputStream} and {@link 
ImageOutputStream} types),
      *           then it is returned unchanged.</li>
-     *
      *       <li>Otherwise if the input is an instance of {@link ByteBuffer}, 
then an {@link ImageInputStream}
      *           backed by a read-only view of that buffer is created when 
first needed and returned.
      *           The properties (position, mark, limit) of the original buffer 
are unmodified.</li>
-     *
      *       <li>Otherwise if the input is an instance of {@link Path}, {@link 
File},
      *           {@link URI}, {@link URL}, {@link CharSequence}, {@link 
InputStream} or
-     *           {@link java.nio.channels.ReadableByteChannel}, then an {@link 
ImageInputStream} backed by a
+     *           {@link ReadableByteChannel}, then an {@link ImageInputStream} 
backed by a
      *           {@link ByteBuffer} is created when first needed and 
returned.</li>
-     *
      *       <li>Otherwise if {@link ImageIO#createImageInputStream(Object)} 
returns a non-null value,
      *           then this value is cached and returned.</li>
-     *
      *       <li>Otherwise this method returns {@code null}.</li>
      *     </ul>
      *   </li>
      *   <li>{@link ImageInputStream}:
      *     <ul>
      *       <li>If the above {@code DataInput} can be created and casted to 
{@code ImageInputStream}, returns it.</li>
-     *
      *       <li>Otherwise this method returns {@code null}.</li>
      *     </ul>
      *   </li>
@@ -771,10 +766,8 @@ public class StorageConnector implements Serializable {
      *     <ul>
      *       <li>If the {@linkplain #getStorage() storage} object is already 
an instance of {@link InputStream},
      *           then it is returned unchanged.</li>
-     *
      *       <li>Otherwise if the above {@code ImageInputStream} can be 
created,
      *           returns a wrapper around that stream.</li>
-     *
      *       <li>Otherwise this method returns {@code null}.</li>
      *     </ul>
      *   </li>
@@ -786,7 +779,31 @@ public class StorageConnector implements Serializable {
      *       <li>Otherwise if the above {@code InputStream} can be created, 
returns an {@link InputStreamReader}
      *           using the encoding specified by {@link OptionKey#ENCODING} if 
any, or using the system default
      *           encoding otherwise.</li>
-     *
+     *       <li>Otherwise this method returns {@code null}.</li>
+     *     </ul>
+     *   </li>
+     *   <li>{@link DataOutput}:
+     *     <ul>
+     *       <li>If the {@linkplain #getStorage() storage} object is already 
an instance of {@code DataOutput}
+     *           (including the {@link ImageOutputStream} type), then it is 
returned unchanged.</li>
+     *       <li>Otherwise if the output is an instance of {@link Path}, 
{@link File},
+     *           {@link URI}, {@link URL}, {@link CharSequence}, {@link 
OutputStream} or
+     *           {@link WritableByteChannel}, then an {@link ImageInputStream} 
backed by a
+     *           {@link ByteBuffer} is created when first needed and 
returned.</li>
+     *       <li>Otherwise if {@link ImageIO#createImageOutputStream(Object)} 
returns a non-null value,
+     *           then this value is cached and returned.</li>
+     *       <li>Otherwise this method returns {@code null}.</li>
+     *     </ul>
+     *   </li>
+     *   <li>{@link ImageOutputStream}:
+     *     <ul>
+     *       <li>If the above {@code DataOutput} can be created and casted to 
{@code ImageOutputStream}, returns it.</li>
+     *       <li>Otherwise this method returns {@code null}.</li>
+     *     </ul>
+     *   </li>
+     *   <li>{@link OutputStream}:
+     *     <ul>
+     *       <li>If the above {@code DataOutput} can be created and casted to 
{@code OutputStream}, returns it.</li>
      *       <li>Otherwise this method returns {@code null}.</li>
      *     </ul>
      *   </li>
@@ -794,10 +811,8 @@ public class StorageConnector implements Serializable {
      *     <ul>
      *       <li>If the {@linkplain #getStorage() storage} object is already 
an instance of {@link Connection},
      *           then it is returned unchanged.</li>
-     *
      *       <li>Otherwise if the storage is an instance of {@link 
DataSource}, then a connection is obtained
      *           when first needed and returned.</li>
-     *
      *       <li>Otherwise this method returns {@code null}.</li>
      *     </ul>
      *   </li>
@@ -805,7 +820,6 @@ public class StorageConnector implements Serializable {
      *     <ul>
      *       <li>If the storage given at construction time is already an 
instance of the requested type,
      *           returns it <i>as-is</i>.</li>
-     *
      *       <li>Otherwise this method throws {@link 
IllegalArgumentException}.</li>
      *     </ul>
      *   </li>
@@ -1109,6 +1123,11 @@ public class StorageConnector implements Serializable {
         } else if (wasProbingAbsentFile()) {
             return null;                            // Do not cache, for 
allowing file creation later.
         } else {
+            /*
+             * This block is executed for storages of unknown type, when 
`ChannelFactory` has no branch for that type.
+             * The Image I/O plugin mechanism allows users to create streams 
from arbitrary objets, so we delegate to
+             * it in last resort.
+             */
             reset();
             try {
                 asDataInput = ImageIO.createImageInputStream(storage);
@@ -1118,14 +1137,9 @@ public class StorageConnector implements Serializable {
             addView(DataInput.class, asDataInput, 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
-             * 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.
+             * This is a complication for us. However in Apache SIS, 
`ImageInputStream` is used only
+             * by WorldFileStore. That store has its own mechanism for closing 
the underlying stream.
+             * So the problem described above would hopefully not occur in 
practice.
              */
         }
         return asDataInput;
@@ -1478,9 +1492,14 @@ public class StorageConnector implements Serializable {
         final DataOutput asDataOutput;
         if (out != null) {
             asDataOutput = out;
-            c = getView(ChannelDataOutput.class);   // Refresh because may 
have been added by createChannelDataInput().
+            c = getView(ChannelDataOutput.class);   // Refresh because may 
have been added by createChannelDataOutput().
             views.put(DataOutput.class, c);         // Share the same 
`Coupled` instance.
         } else {
+            /*
+             * This block is executed for storages of unknown type, when 
`ChannelFactory` has no branch for that type.
+             * The Image I/O plugin mechanism allows users to create streams 
from arbitrary objets, so we delegate to
+             * it in last resort.
+             */
             reset();
             try {
                 asDataOutput = ImageIO.createImageOutputStream(storage);
@@ -1490,18 +1509,42 @@ public class StorageConnector implements Serializable {
             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.
+             * This is a complication for us. However in Apache SIS, 
`ImageOutputStream` is used only
+             * by WorldFileStore. That store has its own mechanism for closing 
the underlying stream.
+             * So the problem described above would hopefully not occur in 
practice.
              */
         }
         return asDataOutput;
     }
 
+    /**
+     * Creates an {@link ImageOutputStream} from the {@link DataOutput} if 
possible. This method casts
+     * {@code DataOutput} if such cast is allowed, or upgrades {@link 
ChannelDataOutput} implementation.
+     *
+     * <p>This method is one of the {@link #OPENERS} methods and should be 
invoked at most once per
+     * {@code StorageConnector} instance.</p>
+     *
+     * @return input stream, or {@code null} if none or if {@linkplain 
#probing} result has been determined offline.
+     */
+    private ImageOutputStream createImageOutputStream() throws IOException, 
DataStoreException {
+        final ImageOutputStream asDataOutput;
+        DataOutput output = getStorageAs(DataOutput.class);
+        if (output instanceof ImageOutputStream) {
+            asDataOutput = (ImageOutputStream) output;
+            Coupled c = views.get(DataOutput.class);
+            views.put(ImageOutputStream.class, c);          // Share the same 
`Coupled` instance.
+        } else {
+            final ChannelDataOutput c = getStorageAs(ChannelDataOutput.class);
+            if (c != null && c.channel instanceof ReadableByteChannel) {
+                asDataOutput = new ChannelImageOutputStream(c);
+            } else {
+                asDataOutput = null;                        // Remember that 
there is no view.
+            }
+            addView(ImageOutputStream.class, asDataOutput, 
ChannelDataOutput.class, (byte) 0);
+        }
+        return asDataOutput;
+    }
+
     /**
      * Creates an output stream from {@link WritableByteChannel} if possible,
      * or from {@link ImageOutputStream} otherwise.

Reply via email to