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); } }