sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1806256 - in /sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis: internal/storage/ internal/storage/io/ storage/
Date Sat, 26 Aug 2017 04:17:42 GMT
Author: desruisseaux
Date: Sat Aug 26 04:17:42 2017
New Revision: 1806256

URL: http://svn.apache.org/viewvc?rev=1806256&view=rev
Log:
Better tracking of inputs that need to have their position synchronized in StorageConnector.

Modified:
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelData.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelDataInput.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelDataOutput.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelFactory.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelImageInputStream.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/ForwardOnlyStorageException.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java?rev=1806256&r1=1806255&r2=1806256&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
[UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
[UTF-8] Sat Aug 26 04:17:42 2017
@@ -169,6 +169,16 @@ public final class Resources extends Ind
         public static final short StreamIsForwardOnly_1 = 13;
 
         /**
+         * Stream “{0}” is not readable.
+         */
+        public static final short StreamIsNotReadable_1 = 25;
+
+        /**
+         * Stream “{0}” is not writable.
+         */
+        public static final short StreamIsNotWritable_1 = 26;
+
+        /**
          * The “{0}” data store can be read only once.
          */
         public static final short StreamIsReadOnce_1 = 18;

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties?rev=1806256&r1=1806255&r2=1806256&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
[ISO-8859-1] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
[ISO-8859-1] Sat Aug 26 04:17:42 2017
@@ -40,6 +40,8 @@ ResourceIdentifierCollision_2     = More
 ResourceNotFound_2                = No resource found for the \u201c{1}\u201d identifier
in the \u201c{0}\u201d data store.
 ShallBeDeclaredBefore_2           = The \u201c{1}\u201d element must be declared before \u201c{0}\u201d.
 StreamIsForwardOnly_1             = Can not move backward in the \u201c{0}\u201d stream.
+StreamIsNotReadable_1             = Stream \u201c{0}\u201d is not readable.
+StreamIsNotWritable_1             = Stream \u201c{0}\u201d is not writable.
 StreamIsReadOnce_1                = The \u201c{0}\u201d data store can be read only once.
 StreamIsWriteOnce_1               = Can not modify previously written data in \u201c{0}\u201d.
 UnknownFormatFor_1                = Format of \u201c{0}\u201d is not recognized.

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties?rev=1806256&r1=1806255&r2=1806256&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
[ISO-8859-1] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
[ISO-8859-1] Sat Aug 26 04:17:42 2017
@@ -45,6 +45,8 @@ ResourceIdentifierCollision_2     = Plus
 ResourceNotFound_2                = Aucune ressource n\u2019a \u00e9t\u00e9 trouv\u00e9e
pour l\u2019identifiant \u00ab\u202f{1}\u202f\u00bb dans les donn\u00e9es de \u00ab\u202f{0}\u202f\u00bb.
 ShallBeDeclaredBefore_2           = L\u2019\u00e9l\u00e9ment \u00ab\u202f{1}\u202f\u00bb
doit \u00eatre d\u00e9clar\u00e9 avant \u00ab\u202f{0}\u202f\u00bb.
 StreamIsForwardOnly_1             = Ne peut pas reculer dans le flux de donn\u00e9es \u00ab\u202f{0}\u202f\u00bb.
+StreamIsNotReadable_1             = Les donn\u00e9es de \u00ab\u202f{0}\u202f\u00bb ne sont
pas accessibles en lecture.
+StreamIsNotWritable_1             = Le flux de donn\u00e9es \u00ab\u202f{0}\u202f\u00bb ne
g\u00e8re pas les \u00e9critures.
 StreamIsReadOnce_1                = Les donn\u00e9es de \u00ab\u202f{0}\u202f\u00bb ne peuvent
\u00eatre lues qu\u2019une seule fois.
 StreamIsWriteOnce_1               = Ne peut pas revenir sur les donn\u00e9es d\u00e9j\u00e0
\u00e9crites dans \u00ab\u202f{0}\u202f\u00bb.
 UnknownFormatFor_1                = Le format de \u00ab\u202f{0}\u202f\u00bb n\u2019est pas
reconnu.

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelData.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelData.java?rev=1806256&r1=1806255&r2=1806256&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelData.java
[UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelData.java
[UTF-8] Sat Aug 26 04:17:42 2017
@@ -62,8 +62,11 @@ public abstract class ChannelData implem
      * The position of the channel when this {@code ChannelData} has been created.
      * This is almost always 0, but we allow other values in case the data to read
      * or write are part of a bigger file.
+     *
+     * <p>This value is added to the argument given to the {@link #seek(long)} method.
Users can ignore
+     * this field, unless they want to invoke {@link SeekableByteChannel#position(long)}
directly.</p>
      */
-    final long channelOffset;
+    public final long channelOffset;
 
     /**
      * The channel position where is located the {@link #buffer} value at index 0.

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelDataInput.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelDataInput.java?rev=1806256&r1=1806255&r2=1806256&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelDataInput.java
[UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelDataInput.java
[UTF-8] Sat Aug 26 04:17:42 2017
@@ -841,7 +841,7 @@ public class ChannelDataInput extends Ch
      */
     @Override
     public final void seek(final long position) throws IOException {
-        long p = position - bufferOffset;
+        long p = Math.subtractExact(position, bufferOffset);
         if (p >= 0 && p <= buffer.limit()) {
             /*
              * Requested position is inside the current limits of the buffer.
@@ -854,7 +854,7 @@ public class ChannelDataInput extends Ch
              * that StorageConnector.rewind() needs the buffer content to be
              * valid as a result of this seek, so we reload it immediately.
              */
-            ((SeekableByteChannel) channel).position(channelOffset + position);
+            ((SeekableByteChannel) channel).position(Math.addExact(channelOffset, position));
             bufferOffset = position;
             buffer.clear().limit(0);
         } else if (p >= 0) {

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelDataOutput.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelDataOutput.java?rev=1806256&r1=1806255&r2=1806256&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelDataOutput.java
[UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelDataOutput.java
[UTF-8] Sat Aug 26 04:17:42 2017
@@ -607,7 +607,7 @@ public class ChannelDataOutput extends C
      */
     @Override
     public final void seek(final long position) throws IOException {
-        long p = position - bufferOffset;
+        long p = Math.subtractExact(position, bufferOffset);
         if (p >= 0 && p <= buffer.limit()) {
             /*
              * Requested position is inside the current limits of the buffer.
@@ -620,7 +620,7 @@ public class ChannelDataOutput extends C
              * but we can set the new position directly in the channel.
              */
             flush();
-            ((SeekableByteChannel) channel).position(channelOffset + position);
+            ((SeekableByteChannel) channel).position(Math.addExact(channelOffset, position));
             bufferOffset = position;
         } else if (p >= 0) {
             /*

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelFactory.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelFactory.java?rev=1806256&r1=1806255&r2=1806256&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelFactory.java
[UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelFactory.java
[UTF-8] Sat Aug 26 04:17:42 2017
@@ -45,6 +45,8 @@ import org.apache.sis.util.logging.Loggi
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.storage.Resources;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.ForwardOnlyStorageException;
 
 
 /**
@@ -244,6 +246,18 @@ public abstract class ChannelFactory {
     }
 
     /**
+     * Returns whether the streams or channels created by this factory is coupled with the
{@code storage} argument
+     * given to the {@link #prepare prepare(…)} method. This is {@code true} if the storage
is an {@link InputStream},
+     * {@link OutputStream} or {@link Channel}, and {@code false} if the storage is a {@link
Path}, {@link File},
+     * {@link URL}, {@link URI} or equivalent.
+     *
+     * @return whether using the streams or channels will affect the original {@code storage}
object.
+     */
+    public boolean isCoupled() {
+        return false;
+    }
+
+    /**
      * Returns {@code true} if this factory is capable to create another reader. This method
returns {@code false}
      * if this factory is capable to create only one channel and {@link #reader(String)}
has already been invoked.
      *
@@ -259,9 +273,10 @@ public abstract class ChannelFactory {
      *
      * @param  filename  data store name to report in case of failure.
      * @return the input stream.
+     * @throws DataStoreException if the channel is read-once.
      * @throws IOException if the input stream or its underlying byte channel can not be
created.
      */
-    public InputStream inputStream(final String filename) throws IOException {
+    public InputStream inputStream(final String filename) throws DataStoreException, IOException
{
         return Channels.newInputStream(reader(filename));
     }
 
@@ -271,9 +286,10 @@ public abstract class ChannelFactory {
      *
      * @param  filename  data store name to report in case of failure.
      * @return the output stream.
+     * @throws DataStoreException if the channel is write-once.
      * @throws IOException if the output stream or its underlying byte channel can not be
created.
      */
-    public OutputStream outputStream(final String filename) throws IOException {
+    public OutputStream outputStream(final String filename) throws DataStoreException, IOException
{
         return Channels.newOutputStream(writer(filename));
     }
 
@@ -284,9 +300,10 @@ public abstract class ChannelFactory {
      *
      * @param  filename  data store name to report in case of failure.
      * @return the channel for the given input.
+     * @throws DataStoreException if the channel is read-once.
      * @throws IOException if an error occurred while opening the channel.
      */
-    public abstract ReadableByteChannel reader(String filename) throws IOException;
+    public abstract ReadableByteChannel reader(String filename) throws DataStoreException,
IOException;
 
     /**
      * Returns a byte channel from the output given to the {@link #prepare prepare(…)}
method.
@@ -295,9 +312,10 @@ public abstract class ChannelFactory {
      *
      * @param  filename  data store name to report in case of failure.
      * @return the channel for the given output.
+     * @throws DataStoreException if the channel is write-once.
      * @throws IOException if an error occurred while opening the channel.
      */
-    public abstract WritableByteChannel writer(String filename) throws IOException;
+    public abstract WritableByteChannel writer(String filename) throws DataStoreException,
IOException;
 
     /**
      * A factory that returns an existing channel <cite>as-is</cite>.
@@ -317,6 +335,14 @@ public abstract class ChannelFactory {
         }
 
         /**
+         * Returns {@code true} since use of channels or streams will affect the original
storage object.
+         */
+        @Override
+        public boolean isCoupled() {
+            return true;
+        }
+
+        /**
          * Returns whether {@link #reader(String)} or {@link #writer(String)} can be invoked.
          */
         @Override
@@ -329,13 +355,19 @@ public abstract class ChannelFactory {
          * throws an exception on all subsequent invocations.
          */
         @Override
-        public ReadableByteChannel reader(final String filename) throws IOException {
+        public ReadableByteChannel reader(final String filename) throws DataStoreException,
IOException {
             final Channel in = channel;
             if (in instanceof ReadableByteChannel) {
                 channel = null;
                 return (ReadableByteChannel) in;
             }
-            throw new IOException(Resources.format(Resources.Keys.StreamIsReadOnce_1, filename));
+            String message = Resources.format(in != null ? Resources.Keys.StreamIsNotReadable_1
+                                                         : Resources.Keys.StreamIsReadOnce_1,
filename);
+            if (in != null) {
+                throw new IOException(message);                     // Stream is not readable.
+            } else {
+                throw new ForwardOnlyStorageException(message);     // Stream has already
been read.
+            }
         }
 
         /**
@@ -343,13 +375,19 @@ public abstract class ChannelFactory {
          * throws an exception on all subsequent invocations.
          */
         @Override
-        public WritableByteChannel writer(final String filename) throws IOException {
-            final Channel in = channel;
-            if (in instanceof WritableByteChannel) {
+        public WritableByteChannel writer(final String filename) throws DataStoreException,
IOException {
+            final Channel out = channel;
+            if (out instanceof WritableByteChannel) {
                 channel = null;
-                return (WritableByteChannel) in;
+                return (WritableByteChannel) out;
+            }
+            String message = Resources.format(out != null ? Resources.Keys.StreamIsNotWritable_1
+                                                          : Resources.Keys.StreamIsWriteOnce_1,
filename);
+            if (out != null) {
+                throw new IOException(message);                     // Stream is not writable.
+            } else {
+                throw new ForwardOnlyStorageException(message);     // Stream has already
been written.
             }
-            throw new IOException(Resources.format(Resources.Keys.StreamIsWriteOnce_1, filename));
         }
     }
 

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelImageInputStream.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelImageInputStream.java?rev=1806256&r1=1806255&r2=1806256&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelImageInputStream.java
[UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelImageInputStream.java
[UTF-8] Sat Aug 26 04:17:42 2017
@@ -344,6 +344,14 @@ loop:   while ((c = read()) >= 0) {
 
     /**
      * Closes the {@linkplain #channel}.
+     * If the channel is backed by an {@link java.io.InputStream}, that stream will be closed
too.
+     *
+     * <div class="section">Departure from Image I/O standard implementation</div>
+     * Java Image I/O wrappers around input/output streams do not close the underlying stream
(see for example
+     * {@link javax.imageio.stream.FileCacheImageInputStream#close()} specification). But
Apache SIS needs the
+     * underlying stream to be closed because we do not keep reference to the original input
stream. Note that
+     * channels created by {@link java.nio.channels.Channels#newChannel(java.io.InputStream)}
close the stream,
+     * which is the desired behavior for this method.
      *
      * @throws IOException if an error occurred while closing the channel.
      */

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/ForwardOnlyStorageException.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/ForwardOnlyStorageException.java?rev=1806256&r1=1806255&r2=1806256&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/ForwardOnlyStorageException.java
[UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/ForwardOnlyStorageException.java
[UTF-8] Sat Aug 26 04:17:42 2017
@@ -58,7 +58,17 @@ public class ForwardOnlyStorageException
     }
 
     /**
-     * Creates a localized exception with a default message saying that the stream is read-once.
+     * Creates an exception with the specified details message and cause.
+     *
+     * @param message  the detail message in the default locale.
+     * @param cause    the cause for this exception.
+     */
+    public ForwardOnlyStorageException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Creates a localized exception with a default message saying that the stream is read-once
or write-once.
      *
      * @param locale    the locale of the message to be returned by {@link #getLocalizedMessage()},
or {@code null}.
      * @param filename  name of the file or data store where the error occurred.

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java?rev=1806256&r1=1806255&r2=1806256&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java
[UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java
[UTF-8] Sat Aug 26 04:17:42 2017
@@ -32,6 +32,7 @@ import java.io.Serializable;
 import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
 import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.SeekableByteChannel;
 import javax.imageio.ImageIO;
 import javax.imageio.stream.ImageInputStream;
 import java.sql.Connection;
@@ -43,6 +44,7 @@ import org.apache.sis.util.ArgumentCheck
 import org.apache.sis.util.ObjectConverters;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.CorruptedObjectException;
+import org.apache.sis.internal.storage.Resources;
 import org.apache.sis.internal.storage.io.IOUtilities;
 import org.apache.sis.internal.storage.io.ChannelFactory;
 import org.apache.sis.internal.storage.io.ChannelDataInput;
@@ -50,6 +52,9 @@ import org.apache.sis.internal.storage.i
 import org.apache.sis.internal.storage.io.InputStreamAdapter;
 import org.apache.sis.setup.OptionKey;
 
+// Branch-dependent imports
+import java.util.function.Consumer;
+
 
 /**
  * Information for creating a connection to a {@link DataStore} in read and/or write mode.
@@ -155,6 +160,14 @@ public class StorageConnector implements
     private transient String extension;
 
     /**
+     * The options, created only when first needed.
+     *
+     * @see #getOption(OptionKey)
+     * @see #setOption(OptionKey, Object)
+     */
+    private Map<OptionKey<?>, Object> options;
+
+    /**
      * Views of {@link #storage} as some of the following supported types:
      *
      * <ul>
@@ -189,6 +202,27 @@ public class StorageConnector implements
     private transient Map<Class<?>, Object> views;
 
     /**
+     * The views that need to be synchronized if {@link #storage} is used independently.
They are views
+     * that may advance {@code storage} position, but not necessarily in same time than the
view position
+     * (typically because the view reads some bytes in advance and stores them in a buffer).
This map may
+     * be non-null when the storage is an {@link InputStream}, {@link java.io.OutputStream}
or a
+     * {@link java.nio.channels.Channel}. Those views can be:
+     *
+     * <ul>
+     *   <li>{@link Reader} that are wrappers around {@code InputStream}.</li>
+     *   <li>{@link ChannelDataInput} when the channel come from an {@code InputStream}.</li>
+     *   <li>{@link ChannelDataInput} when the channel has been explicitely given to
the constructor.</li>
+     * </ul>
+     *
+     * Note that we do <strong>not</strong> include {@link InputStreamAdapter}
because it does not use buffer;
+     * {@code InputStreamAdapter} positions are synchronized with wrapped {@link ImageInputStream}
positions.
+     *
+     * <p>Values are cleanup actions to execute after the {@link #storage} has been
reseted to its original position.
+     * A {@code null} value means that the view can not be synchronized and consequently
should be discarded.</p>
+     */
+    private transient Map<Class<?>, Consumer<StorageConnector>> viewsToSync;
+
+    /**
      * Objects which will need to be closed by the {@link #closeAllExcept(Object)} method.
      * For each (<var>key</var>, <var>value</var>) entry, if the
object to close (the key)
      * is a wrapper around an other object (e.g. an {@link InputStreamReader} wrapping an
@@ -200,12 +234,11 @@ public class StorageConnector implements
     private transient Map<Object, Object> viewsToClose;
 
     /**
-     * The options, created only when first needed.
+     * The view returned by the last call to {@link #getStorageAs(Class)}.
      *
-     * @see #getOption(OptionKey)
-     * @see #setOption(OptionKey, Object)
+     * @see #storage
      */
-    private Map<OptionKey<?>, Object> options;
+    private transient Object lastView;
 
     /**
      * Creates a new data store connection wrapping the given input/output object.
@@ -254,10 +287,15 @@ public class StorageConnector implements
      * The object can be of any type, but the class javadoc lists the most typical ones.
      *
      * @return the input/output object as a URL, file, image input stream, <i>etc.</i>.
+     * @throws DataStoreException if an error occurred while reseting the input stream or
channel to its original position.
      *
      * @see #getStorageAs(Class)
      */
-    public Object getStorage() {
+    public Object getStorage() throws DataStoreException {
+        if (viewsToSync != null && storage != lastView) {
+            resetStorage();
+        }
+        lastView = storage;
         return storage;
     }
 
@@ -414,11 +452,12 @@ public class StorageConnector implements
          * of stream (for example a java.io.Reader); this will be done at the end of this
method.
          */
         Object value;
-        final T view;
+        final boolean cache;
         if (views != null && (value = views.get(type)) != null) {
             if (value == Void.TYPE) return null;
-            view = type.cast(value);
+            cache = false;
         } else {
+            cache = true;
             value = storage;
             if (!type.isInstance(value)) {
                 /*
@@ -448,48 +487,116 @@ public class StorageConnector implements
                     value = ObjectConverters.convert(storage, type);
                 }
             }
-            view = type.cast(value);
-            addView(type, view);                // Cache the result for future reuse.
         }
         /*
          * If the user asked an InputStream, we may return the storage as-is if it was already
an InputStream.
          * However before doing so, we may need to reset the InputStream position if the
stream has been used
-         * by a ChannelDataInput.
+         * by a ChannelDataInput or an InputStreamReader.
          */
-        if (view == storage && view instanceof InputStream) try {
-            resetInputStream();
-        } catch (IOException e) {
-            throw new DataStoreException(Errors.format(Errors.Keys.CanNotRead_1, getStorageName()),
e);
+        final T view = type.cast(value);
+        if (viewsToSync != null && view != lastView && (view == storage ||
viewsToSync.containsKey(type))) {
+            resetStorage();
+        }
+        if (cache) {
+            addView(type, view);                // Shall be after 'resetStorage()'.
         }
+        lastView = view;
         return view;
     }
 
     /**
-     * Assuming that {@link #storage} is an instance of {@link InputStream}, resets its position.
This method
-     * is the converse of the marks performed at the beginning of {@link #createChannelDataInput(boolean)}.
+     * Mark the storage position before to create a view that may be a wrapper around that
storage.
+     */
+    private void markStorage() {
+        if (storage instanceof InputStream) {
+            ((InputStream) storage).mark(DEFAULT_BUFFER_SIZE);
+        }
+    }
+
+    /**
+     * Assuming that {@link #storage} is an instance of {@link InputStream}, {@link ReadableByteChannel}
or other
+     * objects that may be affected by views operations, resets the storage position. This
method is the converse
+     * of {@link #markStorage()}.
      *
      * <div class="note"><b>Rational:</b>
      * {@link DataStoreProvider#probeContent(StorageConnector)} contract requires that implementors
reset the
-     * input stream themselves. However if the last {@code DataStoreProvider} instance that
we tried worked on
-     * {@code ChannelDataInput}, then the provider performed a call to {@link ChannelDataInput#reset()},
which
-     * did not reseted the underlying input stream. So we need to perform the missing {@link
InputStream#reset()}
-     * here, then synchronize the {@code ChannelDataInput} position accordingly.</div>
-     */
-    private void resetInputStream() throws IOException {
-        final ChannelDataInput channel = getView(ChannelDataInput.class);
-        if (channel != null) {
+     * input stream themselves. However if {@link ChannelDataInput} or {@link InputStreamReader}
has been used,
+     * then the user performed a call to {@link ChannelDataInput#reset()} (for instance),
which did not reseted
+     * the underlying input stream. So we need to perform the missing {@link InputStream#reset()}
here, then
+     * synchronize the {@code ChannelDataInput} position accordingly.</div>
+     */
+    private <T> void resetStorage() throws DataStoreException {
+        if (lastView != null) {
             /*
+             * We must reset InputStream or ReadableChannel position before to run cleanup
code.
              * Note on InputStream.reset() behavior documented in java.io:
              *
              *  - It does not discard the mark, so it is okay if reset() is invoked twice.
              *  - If mark is unsupported, may either throw IOException or reset the stream
              *    to an implementation-dependent fixed state.
              */
-            ((InputStream) storage).reset();        // May throw an exception if mark is
unsupported.
-            channel.buffer.limit(0);                // Must be after storage.reset().
-            channel.setStreamPosition(0);           // Must be after buffer.limit(0).
+            boolean isReset = false;
+            IOException cause = null;
+            try {
+                if (storage instanceof InputStream) {
+                    ((InputStream) storage).reset();
+                    isReset = true;
+                } else if (storage instanceof SeekableByteChannel) {
+                    ((SeekableByteChannel) storage).position(getView(ChannelDataInput.class).channelOffset);
+                    isReset = true;
+                }
+            } catch (IOException e) {
+                cause = e;
+            }
+            if (!isReset) {
+                throw new ForwardOnlyStorageException(Resources.format(
+                        Resources.Keys.StreamIsReadOnce_1, getStorageName()), cause);
+            }
+            /*
+             * At this point the InputStream or ReadableChannel has been reset.
+             * Now reset or remove any view that depend on it.
+             */
+            for (final Map.Entry<Class<?>, Consumer<StorageConnector>>
entry : viewsToSync.entrySet()) {
+                final Consumer<StorageConnector> sync = entry.getValue();
+                if (sync != null) {
+                    sync.accept(this);
+                } else {
+                    removeView(entry.getKey());             // Reader will need to be recreated
from scratch.
+                }
+            }
         }
-        removeView(Reader.class);                   // Reader will need to be recreated from
scratch.
+    }
+
+    /**
+     * Resets {@link ChannelDataInput} after the {@link InputStream} has been reseted.
+     * This method is registered in {@link #viewsToSync} when a {@link ChannelDataInput}
is created.
+     */
+    private void resetChannelDataInput() {
+        ChannelDataInput channel = getView(ChannelDataInput.class);     // Should never be
null.
+        channel.buffer.limit(0);                                        // Must be after
storage.reset().
+        channel.setStreamPosition(0);                                   // Must be after
buffer.limit(0).
+    }
+
+    /**
+     * Gets or creates a view for the input as a {@link ChannelDataInput} if possible. If
{@code ChannelDataInput}
+     * instance already exists, then this method returns it as-is (this method does <strong>not</strong>
verify if
+     * the {@code ChannelDataInput} instance is an image input stream). Otherwise a new {@code
ChannelDataInput}
+     * is created (if possible), cached and returned.
+     *
+     * @param  asImageInputStream  whether new {@code ChannelDataInput} should be {@link
ChannelImageInputStream}.
+     *         This argument is ignored if a {@code ChannelDataInput} instance already exists.
+     * @return the existing or new {@code ChannelDataInput}, or {@code null} if none can
be created.
+     */
+    private ChannelDataInput getChannelDataInput(final boolean asImageInputStream) throws
IOException, DataStoreException {
+        if (views != null) {
+            final Object view = views.get(ChannelDataInput.class);
+            if (view != null) {
+                return (view != Void.TYPE) ? (ChannelDataInput) view : null;
+            }
+        }
+        final ChannelDataInput view = createChannelDataInput(asImageInputStream);       //
May be null.
+        addView(ChannelDataInput.class, view);                                          //
Cache even if null.
+        return view;
     }
 
     /**
@@ -501,16 +608,14 @@ public class StorageConnector implements
      * @param  asImageInputStream  whether the {@code ChannelDataInput} needs to be {@link
ChannelImageInputStream} subclass.
      * @throws IOException if an error occurred while opening a channel for the input.
      */
-    private ChannelDataInput createChannelDataInput(final boolean asImageInputStream) throws
IOException {
+    private ChannelDataInput createChannelDataInput(final boolean asImageInputStream) throws
IOException, DataStoreException {
         /*
          * Before to try to wrap an InputStream, mark its position so we can rewind if the
user asks for
          * the InputStream directly. We need to reset because ChannelDataInput may have read
some bytes.
          * Note that if mark is unsupported, the default InputStream.mark() implementation
does nothing.
-         * See above 'resetInputStream()' method.
+         * See above 'resetStorage()' method.
          */
-        if (storage instanceof InputStream) {
-            ((InputStream) storage).mark(DEFAULT_BUFFER_SIZE);
-        }
+        markStorage();
         /*
          * Following method call recognizes ReadableByteChannel, InputStream (with special
case for FileInputStream),
          * URL, URI, File, Path or other types that may be added in future Apache SIS versions.
@@ -545,6 +650,14 @@ public class StorageConnector implements
         if (factory.canOpen()) {
             addView(ChannelFactory.class, factory);
         }
+        /*
+         * If the channels to be created by ChannelFactory are wrappers around InputStream
or any other object
+         * that may be affected when read operations will occur, we need to remember that
fact in order to keep
+         * the storage synchronized with the view.
+         */
+        if (factory.isCoupled()) {
+            addViewToSync(ChannelDataInput.class, StorageConnector::resetChannelDataInput);
+        }
         return asDataInput;
     }
 
@@ -559,20 +672,24 @@ public class StorageConnector implements
      *
      * @throws IOException if an error occurred while opening a stream for the input.
      */
-    private DataInput createDataInput() throws IOException {
+    private DataInput createDataInput() throws IOException, DataStoreException {
         /*
          * Creates a ChannelImageInputStream instance. We really need that specific type
because some
          * SIS data stores will want to access directly the channel and the buffer. We will
fallback
          * on the ImageIO.createImageInputStream(Object) method only in last resort.
          */
-        if (views == null || !views.containsKey(ChannelDataInput.class)) {
-            addView(ChannelDataInput.class, createChannelDataInput(true));
-        }
-        final ChannelDataInput c = getView(ChannelDataInput.class);
+        final ChannelDataInput c = getChannelDataInput(true);
         final DataInput asDataInput;
         if (c == null) {
             asDataInput = ImageIO.createImageInputStream(storage);
             addViewToClose(asDataInput, storage);
+            /*
+             * 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.
+             */
         } else if (c instanceof DataInput) {
             asDataInput = (DataInput) c;
             // No call to 'addViewToClose' because it has already be done by createChannelDataInput(…).
@@ -605,10 +722,7 @@ public class StorageConnector implements
          * First, try to create the ChannelDataInput if it does not already exists.
          * If successful, this will create a ByteBuffer companion as a side effect.
          */
-        if (views == null || !views.containsKey(ChannelDataInput.class)) {
-            addView(ChannelDataInput.class, createChannelDataInput(false));
-        }
-        final ChannelDataInput c = getView(ChannelDataInput.class);
+        final ChannelDataInput c = getChannelDataInput(false);
         if (c != null) {
             return c.buffer.asReadOnlyBuffer();
         }
@@ -718,25 +832,27 @@ public class StorageConnector implements
      */
     private Reader createReader() throws DataStoreException {
         final InputStream input = getStorageAs(InputStream.class);
-        if (input != null) {
-            final Charset encoding = getOption(OptionKey.ENCODING);
-            final Reader c = (encoding != null) ? new InputStreamReader(input, encoding)
-                                                : new InputStreamReader(input);
-            /*
-             * Current implementation does not wrap the above Reader in a BufferedReader
because:
-             *
-             * 1) InputStreamReader already uses a buffer internally.
-             * 2) InputStreamReader does not support mark/reset, which is a desired limitation
for now.
-             *    This is because reseting the Reader would not reset the underlying InputStream,
which
-             *    would cause other DataStoreProvider.probeContent(…) methods to fail if
they try to use
-             *    the InputStream. For now we let the InputStreamReader.mark() to throw an
IOException,
-             *    but we may need to provide our own subclass of BufferedReader in a future
SIS version
-             *    if mark/reset support is needed here.
-             */
-            addViewToClose(c, input);
-            return c;
+        if (input == null) {
+            return null;
         }
-        return null;
+        markStorage();
+        final Charset encoding = getOption(OptionKey.ENCODING);
+        final Reader c = (encoding != null) ? new InputStreamReader(input, encoding)
+                                            : new InputStreamReader(input);
+        /*
+         * Current implementation does not wrap the above Reader in a BufferedReader because:
+         *
+         * 1) InputStreamReader already uses a buffer internally.
+         * 2) InputStreamReader does not support mark/reset, which is a desired limitation
for now.
+         *    This is because reseting the Reader would not reset the underlying InputStream,
which
+         *    would cause other DataStoreProvider.probeContent(…) methods to fail if they
try to use
+         *    the InputStream. For now we let the InputStreamReader.mark() to throw an IOException,
+         *    but we may need to provide our own subclass of BufferedReader in a future SIS
version
+         *    if mark/reset support is needed here.
+         */
+        addViewToClose(c, input);
+        addViewToSync(Reader.class, null);
+        return c;
     }
 
     /**
@@ -806,6 +922,24 @@ public class StorageConnector implements
         }
     }
 
+    /**
+     * Declares that the view of the given type is coupled with {@link #storage}.
+     * A change of view position will change storage position, and vis-versa.
+     * See {@link #viewsToSync} for more information.
+     *
+     * @param  sync  action to execute after {@link #storage} has been reset,
+     *               or {@code null} if the view should be removed.
+     */
+    private void addViewToSync(final Class<?> type, final Consumer<StorageConnector>
sync) {
+        if (viewsToSync == null) {
+            viewsToSync = new IdentityHashMap<>(4);
+        }
+        if (viewsToSync.put(type, sync) != null) {
+            // Should never happen, unless someone used this StorageConnector in another
thread.
+            throw new ConcurrentModificationException();
+        }
+    }
+
     /**
      * Declares that the given {@code input} will need to be closed by the {@link #closeAllExcept(Object)}
method.
      * The {@code input} argument is always a new instance wrapping, directly or indirectly,
the {@link #storage}.



Mime
View raw message