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 3858503ae455572140d13cf99f21bc1bdd3747cf
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Sat Sep 16 11:04:01 2023 +0200

    Documentation and code updates in I/O in preparation for GeoTIFF writer.
---
 .../apache/sis/io/stream/ChannelDataOutput.java    | 87 ++++++++++++++--------
 .../apache/sis/io/stream/HyperRectangleReader.java |  2 +-
 .../main/org/apache/sis/io/stream/Region.java      | 38 +++++++---
 .../org/apache/sis/storage/base/URIDataStore.java  |  7 +-
 4 files changed, 86 insertions(+), 48 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelDataOutput.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelDataOutput.java
index ea8c18fff7..31e17058bb 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelDataOutput.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelDataOutput.java
@@ -436,6 +436,12 @@ public class ChannelDataOutput extends ChannelData 
implements Flushable {
      * in order to avoid duplicating almost identical code many times.
      */
     private abstract class ArrayWriter {
+        /**
+         * Creates a new writer.
+         */
+        ArrayWriter() {
+        }
+
         /**
          * Creates a new buffer of the type required by the array to write.
          * This method is guaranteed to be invoked exactly once.
@@ -459,11 +465,11 @@ public class ChannelDataOutput extends ChannelData 
implements Flushable {
         }
 
         /**
-         * Writes {@code length} characters from the array to the stream.
+         * Writes {@code length} elements from the array to the stream.
          *
          * @param  dataSize  the size of the Java primitive type which is the 
element of the array.
          * @param  offset    the starting position within {@code src} to write.
-         * @param  length    the number of characters to write.
+         * @param  length    the number of elements to write.
          * @throws IOException if an error occurred while writing the stream.
          */
         final void writeFully(final int dataSize, int offset, int length) 
throws IOException {
@@ -580,17 +586,51 @@ public class ChannelDataOutput extends ChannelData 
implements Flushable {
     }
 
     /**
-     * Fills the buffer with the zero values from its position up to the limit.
-     * After this method call, the position is undetermined and shall be set to
-     * a new value by the caller.
+     * Repeats the same byte many times.
+     * This method can be used for filling a region of the output stream.
+     *
+     * @param  count  number of bytes to write.
+     * @param  value  the byte to repeat the given amount of times.
      */
-    private void clear() {
-        if (buffer.hasArray()) {
-            final int offset = buffer.arrayOffset();
-            Arrays.fill(buffer.array(), offset + buffer.position(), offset + 
buffer.limit(), (byte) 0);
-        } else {
-            while (buffer.hasRemaining()) {
-                buffer.put((byte) 0);
+    public final void repeat(long count, final byte value) throws IOException {
+        clearBitOffset();
+        if (count > 0) {
+            /*
+             * Fill the buffer with the specified value. The filling is done 
only once,
+             * even if the number of bytes to write is greater than the buffer 
capacity.
+             * We can do that because the same buffer content can be reused 
during each
+             * `WritableByteChannel.write(ByteBuffer)` call.
+             */
+            long n = Math.min(count, buffer.capacity());
+            ensureBufferAccepts((int) n);
+            if (buffer.hasArray()) {
+                final int offset = buffer.arrayOffset();
+                final int lower  = buffer.position();
+                final int upper  = lower + (int) n;
+                Arrays.fill(buffer.array(), offset + lower, offset + upper, 
value);
+                buffer.position(upper);
+            } else {
+                for (int i = (int) n; --i >= 0;) {
+                    buffer.put(value);
+                }
+            }
+            /*
+             * If the number of bytes to write is greater than the capacity, 
we need to flush the buffer.
+             * Not necessarily fully however, because maybe there is not so 
much extra bytes to write.
+             */
+            if ((count -= n) > 0) {                                 // What 
remains, not counting what we put in the buffer.
+                assert buffer.position() == buffer.capacity();      // Because 
of `ensureBufferAccepts(capacity)`.
+                int c;
+                do {
+                    c = channel.write(buffer.rewind());
+                    bufferOffset += c;
+                    if (c == 0) {
+                        onEmptyTransfer();
+                    }
+                    c = buffer.remaining();                         // 
Remaining data that were not written.
+                    n = Math.min(count, buffer.capacity() - c);     // Number 
of bytes to append in the buffer.
+                } while ((count -= n) > 0);                         // What 
remains, not counting what we put in the buffer.
+                buffer.limit(c + (int) n);                          // Set 
also the position to the limit.
             }
         }
     }
@@ -620,34 +660,17 @@ public class ChannelDataOutput extends ChannelData 
implements Flushable {
             flush();
             ((SeekableByteChannel) 
channel).position(Math.addExact(channelOffset, position));
             bufferOffset = position;
-        } else if (p >= 0) {
+        } else if ((p -= buffer.position()) >= 0) {
             /*
              * Requested position is after the current buffer limit and
              * we cannot seek, so we have to pad with some zero values.
              */
-            p -= buffer.limit();
-            flush();                      // Also set the position to 0.
-            if (p <= buffer.capacity()) {
-                buffer.limit((int) p);
-                clear();
-                buffer.position((int) p);
-            } else {
-                buffer.clear();
-                clear();
-                do {
-                    if (channel.write(buffer) == 0) {
-                        onEmptyTransfer();
-                    }
-                    bufferOffset += buffer.position();
-                    p -= buffer.position();
-                    buffer.rewind();
-                } while (p > buffer.capacity());
-                buffer.limit((int) p).position((int) p);
-            }
+            repeat(p, (byte) 0);
         } else {
             // We cannot move position beyond the buffered part.
             throw new 
IOException(Resources.format(Resources.Keys.StreamIsForwardOnly_1, filename));
         }
+        assert super.getStreamPosition() == position;
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/HyperRectangleReader.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/HyperRectangleReader.java
index 19cf411dc1..5d7274adab 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/HyperRectangleReader.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/HyperRectangleReader.java
@@ -201,7 +201,7 @@ loop:       do {
                      * new row, or a new plane, or a new cube?). This 
determines how many bytes we have to
                      * skip.
                      */
-                    if (++cursor[i] < 
region.targetSize[contiguousDataDimension + i]) {
+                    if (++cursor[i] < 
region.getTargetSize(contiguousDataDimension + i)) {
                         streamPosition = Math.addExact(streamPosition, 
strides[i]);
                         arrayPosition  = Math.addExact(arrayPosition, 
contiguousDataLength);
                         continue loop;
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/Region.java 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/Region.java
index 35990369f4..41edf8efbf 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/Region.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/Region.java
@@ -28,10 +28,10 @@ import static org.apache.sis.util.internal.Numerics.ceilDiv;
 
 /**
  * A sub-area in a <var>n</var>-dimensional hyper-rectangle, optionally with 
subsampling.
- * The size of the hyper-rectangle is given by the {@code size} argument at 
construction time,
- * where {@code size.length} is the number of dimensions and {@code size[i]} 
is the number of
- * values along dimension <var>i</var>. For each dimension, the index ranges 
from 0 inclusive
- * to {@code size[i]} exclusive.
+ * The size of the hyper-rectangle is given by the {@code sourceSize} argument 
at construction time,
+ * where {@code sourceSize.length} is the number of dimensions
+ * and {@code sourceSize[i]} is the number of values along dimension 
<var>i</var>.
+ * For each dimension, the index ranges from 0 inclusive to {@code 
sourceSize[i]} exclusive.
  *
  * <p>This class assumes that the values are stored in a sequence (array or 
uncompressed file)
  * where index at dimension 0 varies fastest, followed by index at dimension 
1, <i>etc</i>.</p>
@@ -47,9 +47,10 @@ public final class Region {
      * The size after reading only the sub-region at the given subsampling.
      * The length of this array is the hyper-rectangle dimension.
      *
+     * @see #getTargetSize(int)
      * @see #targetLength(int)
      */
-    final int[] targetSize;
+    private final int[] targetSize;
 
     /**
      * Position of the first value to read.
@@ -82,7 +83,7 @@ public final class Region {
 
     /**
      * Total length of the region.
-     * This is the product of all values in the {@code size} argument given to 
the constructor.
+     * This is the product of all values in the {@code sourceSize} argument 
given to the constructor.
      */
     public final long length;
 
@@ -90,22 +91,22 @@ public final class Region {
      * Creates a new region. It is caller's responsibility to ensure that:
      * <ul>
      *   <li>all arrays have the same length</li>
-     *   <li>{@code size[i] > 0} for all <var>i</var></li>
+     *   <li>{@code sourceSize[i] > 0} for all <var>i</var></li>
      *   <li>{@code regionLower[i] >= 0} for all <var>i</var></li>
-     *   <li>{@code regionLower[i] < regionUpper[i] <= size[i]} for all 
<var>i</var></li>
+     *   <li>{@code regionLower[i] < regionUpper[i] <= sourceSize[i]} for all 
<var>i</var></li>
      *   <li>{@code subsampling[i] > 0} for all <var>i</var></li>
      *   <li>The total length of data to read does not exceed {@link 
Integer#MAX_VALUE}.</li>
      * </ul>
      *
-     * @param  size         the number of elements along each dimension.
+     * @param  sourceSize   the number of elements along each dimension.
      * @param  regionLower  indices of the first value to read or write along 
each dimension.
      * @param  regionUpper  indices after the last value to read or write 
along each dimension.
      * @param  subsampling  subsampling along each dimension. Shall be greater 
than zero.
      * @throws ArithmeticException if the size of the region to read exceeds 
{@link Integer#MAX_VALUE},
      *                             or the total hyper-cube size exceeds {@link 
Long#MAX_VALUE}.
      */
-    public Region(final long[] size, final long[] regionLower, final long[] 
regionUpper, final int[] subsampling) {
-        final int dimension = size.length;
+    public Region(final long[] sourceSize, final long[] regionLower, final 
long[] regionUpper, final int[] subsampling) {
+        final int dimension = sourceSize.length;
         targetSize = new int[dimension];
         skips = new long[dimension + 1];
         long position = 0;
@@ -116,7 +117,7 @@ public final class Region {
             final long lower = regionLower[i];
             final long count = ceilDiv(subtractExact(regionUpper[i], lower), 
step);
             final long upper = addExact(lower, 
incrementExact(multiplyExact(count-1, step)));
-            final long span  = size[i];
+            final long span  = sourceSize[i];
             assert (count > 0) && (lower >= 0) && (upper > lower) && (upper <= 
span) : i;
 
             targetSize[i] = toIntExact(count);
@@ -168,6 +169,8 @@ public final class Region {
     /**
      * Number of dimensions for which we can collapse the read operations in a 
single operation because their
      * data are contiguous. This is the index of the first non-zero element in 
the {@link #skips} array.
+     *
+     * @return number of dimensions which can be transferred in a single I/O 
operation.
      */
     final int contiguousDataDimension() {
         final int dimension = skips.length - 1;
@@ -221,9 +224,20 @@ public final class Region {
         return stride;
     }
 
+    /**
+     * {@return the number of values to skip after having read values in the 
given dimension}.
+     */
+    final long getSkip(final int dimension) {
+        return skips[dimension];
+    }
+
     /**
      * Returns the total number of values to be read from the sub-region while 
applying the subsampling.
      * This method takes in account only the given number of dimensions.
+     *
+     * @param  dimension  number of dimensions to use.
+     * @return number of values to read. Always greater than zero.
+     * @throws ArithmeticException if the size is too large.
      */
     final int targetLength(final int dimension) {
         long size = 1;
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/URIDataStore.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/URIDataStore.java
index 955ed16e29..acfbb6cb9b 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/URIDataStore.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/URIDataStore.java
@@ -17,7 +17,9 @@
 package org.apache.sis.storage.base;
 
 import java.util.Optional;
+import java.io.DataInput;
 import java.io.DataOutput;
+import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.File;
 import java.net.URI;
@@ -323,9 +325,8 @@ public abstract class URIDataStore extends DataStore 
implements StoreResource, R
          */
         public static boolean isWritable(final StorageConnector connector) 
throws DataStoreException {
             final Object storage = connector.getStorage();
-            if (storage instanceof OutputStream || storage instanceof 
DataOutput) {
-                return true;
-            }
+            if (storage instanceof OutputStream || storage instanceof 
DataOutput) return true;    // Must be tested first.
+            if (storage instanceof InputStream  || storage instanceof 
DataInput)  return false;   // Ignore options.
             return 
ArraysExt.contains(connector.getOption(OptionKey.OPEN_OPTIONS), 
StandardOpenOption.WRITE);
         }
     }

Reply via email to