You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2023/01/20 17:05:24 UTC

[sis] 02/02: When reading consecutive tiles in a GeoTIFF file, use a single HTTP request for all contiguous tiles instead of creating a new connection unconditionally for each tile.

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 0f1c4f6b9a557d584b44f1ca6970eb422f50347d
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Fri Jan 20 18:03:44 2023 +0100

    When reading consecutive tiles in a GeoTIFF file, use a single HTTP request for all contiguous tiles instead of creating a new connection unconditionally for each tile.
---
 .../org/apache/sis/util/collection/RangeSet.java   | 45 ++++++++++--
 .../apache/sis/util/collection/RangeSetTest.java   | 51 ++++++++++---
 .../storage/inflater/CompressionChannel.java       |  2 +-
 .../org/apache/sis/storage/geotiff/DataSubset.java | 45 ++++++++++--
 .../sis/internal/storage/io/ChannelDataInput.java  | 16 ++--
 .../internal/storage/io/FileCacheByteChannel.java  | 85 ++++++++++++++++------
 .../storage/io/FileCacheByteChannelTest.java       |  2 +-
 7 files changed, 194 insertions(+), 52 deletions(-)

diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/collection/RangeSet.java b/core/sis-utility/src/main/java/org/apache/sis/util/collection/RangeSet.java
index 1bbf68e843..62b0ad35bf 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/collection/RangeSet.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/collection/RangeSet.java
@@ -331,7 +331,7 @@ public class RangeSet<E extends Comparable<? super E>> extends AbstractSet<Range
     @Override
     public int size() {
         assert (length & 1) == 0;                                   // Length must be even.
-        return length >>> 1;
+        return length >> 1;
     }
 
     /**
@@ -1407,7 +1407,40 @@ public class RangeSet<E extends Comparable<? super E>> extends AbstractSet<Range
             // The value is equal to an excluded endpoint.
             return -1;
         }
-        return index >>> 1;     // Round toward 0 (odd index are maximum values).
+        return index >> 1;      // Round toward 0 (odd index are maximum values).
+    }
+
+    /**
+     * Returns the index of the range having a minimum value equal or lower than the specified value.
+     * If the given value is lower than the minimal value of all ranges in this set,
+     * then this method returns -1.
+     *
+     * @param  value  the minimum value to search, ignoring inclusiveness/exclusiveness.
+     * @return index of the range having a minimum value equal or lower than the specified value. May be -1.
+     *
+     * @since 1.4
+     */
+    public int indexOfMin(final E value) {
+        int index = binarySearch(value, 0, length);
+        if (index < 0) index = ~index - 1;
+        return index >> 1;                  // Not >>> because we need to preserve the sign.
+    }
+
+    /**
+     * Returns the index of the range having a maximum value equal or greater than the specified value.
+     * If the given value is greater than the maximal value of all ranges in this set,
+     * then this method returns {@link #size()}.
+     *
+     * @param  value  the maximum value to search, ignoring inclusiveness/exclusiveness.
+     * @return index of the range having a maximum value equal or greater than the specified value.
+     *         May be {@link #size()}.
+     *
+     * @since 1.4
+     */
+    public int indexOfMax(final E value) {
+        int index = binarySearch(value, 0, length);
+        if (index < 0) index = ~index;
+        return index >> 1;
     }
 
     /**
@@ -1422,7 +1455,7 @@ public class RangeSet<E extends Comparable<? super E>> extends AbstractSet<Range
      * @throws ClassCastException if range elements are not convertible to {@code long}.
      */
     public long getMinLong(final int index) throws IndexOutOfBoundsException, ClassCastException {
-        return Array.getLong(array, Objects.checkIndex(index, length >>> 1) << 1);
+        return Array.getLong(array, Objects.checkIndex(index, length >> 1) << 1);
     }
 
     /**
@@ -1439,7 +1472,7 @@ public class RangeSet<E extends Comparable<? super E>> extends AbstractSet<Range
      * @see org.apache.sis.measure.NumberRange#getMinDouble()
      */
     public double getMinDouble(final int index) throws IndexOutOfBoundsException, ClassCastException {
-        return Array.getDouble(array, Objects.checkIndex(index, length >>> 1) << 1);
+        return Array.getDouble(array, Objects.checkIndex(index, length >> 1) << 1);
     }
 
     /**
@@ -1454,7 +1487,7 @@ public class RangeSet<E extends Comparable<? super E>> extends AbstractSet<Range
      * @throws ClassCastException if range elements are not convertible to {@code long}.
      */
     public long getMaxLong(final int index) throws IndexOutOfBoundsException, ClassCastException {
-        return Array.getLong(array, (Objects.checkIndex(index, length >>> 1) << 1) | 1);
+        return Array.getLong(array, (Objects.checkIndex(index, length >> 1) << 1) | 1);
     }
 
     /**
@@ -1471,7 +1504,7 @@ public class RangeSet<E extends Comparable<? super E>> extends AbstractSet<Range
      * @see org.apache.sis.measure.NumberRange#getMaxDouble()
      */
     public double getMaxDouble(final int index) throws IndexOutOfBoundsException, ClassCastException {
-        return Array.getDouble(array, (Objects.checkIndex(index, length >>> 1) << 1) | 1);
+        return Array.getDouble(array, (Objects.checkIndex(index, length >> 1) << 1) | 1);
     }
 
     /**
diff --git a/core/sis-utility/src/test/java/org/apache/sis/util/collection/RangeSetTest.java b/core/sis-utility/src/test/java/org/apache/sis/util/collection/RangeSetTest.java
index 8643d991cc..3a0955298f 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/util/collection/RangeSetTest.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/util/collection/RangeSetTest.java
@@ -43,7 +43,7 @@ import static org.apache.sis.internal.util.StandardDateFormat.NANOS_PER_SECOND;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Rémi Maréchal (Geomatys)
- * @version 0.5
+ * @version 1.4
  * @since   0.3
  */
 @DependsOn(org.apache.sis.measure.RangeTest.class)
@@ -225,6 +225,8 @@ public final class RangeSetTest extends TestCase {
 
     /**
      * Tests the {@link RangeSet#indexOfRange(Comparable)} method.
+     * Opportunistically tests {@link RangeSet#indexOfMin(Comparable)}
+     * and {@link RangeSet#indexOfMax(Comparable)} methods as well.
      */
     @Test
     public void testIndexOfRange() {
@@ -234,14 +236,45 @@ public final class RangeSetTest extends TestCase {
         assertTrue(ranges.add(-20, -10));
         assertTrue(ranges.add( 60,  70));
         assertTrue(ranges.add( -5,  25));
-        assertEquals( 0, ranges.indexOfRange(-15));
-        assertEquals( 1, ranges.indexOfRange( 20));
-        assertEquals( 2, ranges.indexOfRange( 28));
-        assertEquals( 3, ranges.indexOfRange( 49));
-        assertEquals( 4, ranges.indexOfRange( 69));
-        assertEquals(-1, ranges.indexOfRange( 70));
-        assertEquals(-1, ranges.indexOfRange( 26));
-        assertEquals(-1, ranges.indexOfRange(-30));
+        verifyIndexOf(ranges, -15,  0,  0,  0);
+        verifyIndexOf(ranges,  20,  1,  1,  1);
+        verifyIndexOf(ranges,  28,  2,  2,  2);
+        verifyIndexOf(ranges,  49,  3,  3,  3);
+        verifyIndexOf(ranges,  69,  4,  4,  4);
+        verifyIndexOf(ranges,  70, -1,  4,  4);
+        verifyIndexOf(ranges, 100, -1,  4,  5);
+        verifyIndexOf(ranges,  60,  4,  4,  4);
+        verifyIndexOf(ranges,  59, -1,  3,  4);
+        verifyIndexOf(ranges,  26, -1,  1,  2);
+        verifyIndexOf(ranges, -30, -1, -1,  0);
+        verifyIndexOf(ranges, -20,  0,  0,  0);
+        verifyIndexOf(ranges, -21, -1, -1,  0);
+    }
+
+    /**
+     * Verifies the result of calling an {@code indexOf(…)} method.
+     *
+     * @param ranges  the ranges where to search.
+     * @param value   the value to search.
+     * @param index   expected result of {@code indedOfRange(…)}.
+     * @param min     expected result of {@code indedOfMin(…)}.
+     * @param max     expected result of {@code indedOfMax(…)}.
+     */
+    private static void verifyIndexOf(final RangeSet<Integer> ranges,
+            final int value, final int index, final int min, final int max)
+    {
+        assertEquals(index, ranges.indexOfRange(value));
+        assertEquals(min,   ranges.indexOfMin  (value));
+        assertEquals(max,   ranges.indexOfMax  (value));
+        if (index >= 0) {
+            assertTrue(value >= ranges.getMinLong(index));
+            assertTrue(value <  ranges.getMaxLong(index));
+        }
+        final int s = ranges.size();
+        if (min   >= 0) assertTrue(value >= ranges.getMinLong(min  ));
+        if (min+1 <  s) assertTrue(value <  ranges.getMinLong(min+1));
+        if (max   <  s) assertTrue(value <= ranges.getMaxLong(max  ));
+        if (max   >  0) assertTrue(value >  ranges.getMaxLong(max-1));
     }
 
     /**
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/CompressionChannel.java b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/CompressionChannel.java
index bef43cb3d0..1fd76a6d98 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/CompressionChannel.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/CompressionChannel.java
@@ -84,7 +84,7 @@ abstract class CompressionChannel extends PixelChannel {
     public void setInputRegion(final long start, final long byteCount) throws IOException {
         endPosition = Math.addExact(start, byteCount);
         input.seek(start);
-        input.endOfInterest(endPosition);
+        input.rangeOfInterest(start, endPosition);
     }
 
     /**
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java
index 021c793d8e..fa795ad802 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java
@@ -29,6 +29,7 @@ import org.opengis.util.GenericName;
 import org.apache.sis.image.DataType;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreContentException;
+import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.internal.storage.io.Region;
 import org.apache.sis.internal.storage.io.HyperRectangleReader;
 import org.apache.sis.internal.storage.TiledGridCoverage;
@@ -66,7 +67,7 @@ import static java.lang.Math.toIntExact;
  * the same tile indices than {@link DataCube} in order to avoid integer overflow.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
  * @since   1.1
  */
 class DataSubset extends TiledGridCoverage implements Localized {
@@ -248,7 +249,10 @@ class DataSubset extends TiledGridCoverage implements Localized {
         /**
          * Stores information about a tile to be loaded.
          *
-         * @param iterator  the iterator for which to create a snapshot of its current position.
+         * @param domain         the iterator for which to create a snapshot of its current position.
+         * @param tileOffsets    the {@link DataSubset#tileOffsets} vector.
+         * @param includedBanks  indices of banks to read, or {@code null} for reading all of them.
+         * @param numTiles       value of {@link DataSubset#numTiles} (total number of tiles in the image).
          */
         Tile(final AOI domain, final Vector tileOffsets, final int[] includedBanks, final int numTiles) {
             super(domain);
@@ -259,6 +263,24 @@ class DataSubset extends TiledGridCoverage implements Localized {
             byteOffset = tileOffsets.longValue(p);
         }
 
+        /**
+         * Notifies the input channel about the range of bytes that we are going to read.
+         *
+         * @param tileOffsets    the {@link DataSubset#tileOffsets} vector.
+         * @param tileByteCounts the {@link DataSubset#tileByteCounts} vector.
+         * @param b              indices of banks to read.
+         * @param numTiles       value of {@link DataSubset#numTiles} (total number of tiles in the image).
+         * @param input          the input to notify about the ranges of bytes to read.
+         */
+        final void notifyInputChannel(final Vector tileOffsets, final Vector tileByteCounts,
+                                      int b, final int numTiles, final ChannelDataInput input)
+        {
+            b = indexInTileVector + b * numTiles;
+            final long offset = tileOffsets.longValue(b);
+            final long length = tileByteCounts.longValue(b);
+            input.rangeOfInterest(offset, Numerics.saturatingAdd(offset, length));
+        }
+
         /**
          * Copies {@link #tileOffsets} or {@link #tileByteCounts} values into the given target array.
          * Values for different planes ("banks" in Java2D terminology) are packed as consecutive values
@@ -308,6 +330,7 @@ class DataSubset extends TiledGridCoverage implements Localized {
          * Each tile will either store all sample values in an interleaved fashion inside a single bank
          * (`sourcePixelStride` > 1) or use one separated bank per band (`sourcePixelStride` == 1).
          */
+        final ChannelDataInput input = source.reader.input;
         final int[] includedBanks = (sourcePixelStride == 1) ? includedBands : null;
         final Raster[] result = new Raster[iterator.tileCountInQuery];
         final Tile[] missings = new Tile[iterator.tileCountInQuery];
@@ -319,8 +342,20 @@ class DataSubset extends TiledGridCoverage implements Localized {
                 if (tile != null) {
                     result[iterator.getIndexInResultArray()] = tile;
                 } else {
-                    // Tile not yet loaded. Add to a queue of tiles to load later.
-                    missings[numMissings++] = new Tile(iterator, tileOffsets, includedBanks, numTiles);
+                    /*
+                     * Tile not yet loaded. Add to a queue of tiles to load later.
+                     * Notify the input channel about the ranges of bytes to read.
+                     * This notification is redundant with the same notification
+                     * done in `CompressionChannel.setInputRegion(…)`, but doing
+                     * all notifications in advance gives a chance to group ranges.
+                     */
+                    final Tile missing = new Tile(iterator, tileOffsets, includedBanks, numTiles);
+                    missings[numMissings++] = missing;
+                    if (includedBanks == null) {
+                        missing.notifyInputChannel(tileOffsets, tileByteCounts, 0, numTiles, input);
+                    } else for (int b : includedBanks) {
+                        missing.notifyInputChannel(tileOffsets, tileByteCounts, b, numTiles, input);
+                    }
                 }
             } while (iterator.next());
             if (numMissings != 0) {
@@ -339,7 +374,7 @@ class DataSubset extends TiledGridCoverage implements Localized {
                 final Point  origin      = new Point();
                 final long[] offsets     = new long[numBanks];
                 final long[] byteCounts  = new long[numBanks];
-                try (Closeable c = createInflater()) {
+                try (Closeable finisher  = createInflater()) {
                     for (int i=0; i<numMissings; i++) {
                         final Tile tile = missings[i];
                         if (tile.getRegionInsideTile(lower, upper, subsampling, BIDIMENSIONAL)) {
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelDataInput.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelDataInput.java
index 538ed96daf..40eb30dbe9 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelDataInput.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelDataInput.java
@@ -949,18 +949,18 @@ public class ChannelDataInput extends ChannelData {
     }
 
     /**
-     * Specifies the position after the last byte which is expected to be read.
-     * The number of bytes is only a hint and may be ignored, depending on the channel.
+     * Specifies a range of bytes which is expected to be read.
+     * The range of bytes is only a hint and may be ignored, depending on subclasses.
      * Reading more bytes than specified is okay, only potentially less efficient.
-     * Values ≤ {@linkplain #position() position} means to read until the end of stream.
      *
-     * @param  position  position after the last desired byte,
-     *         or a value ≤ current position for reading until the end of stream.
+     * @param  lower  position (inclusive) of the first byte to be requested.
+     * @param  upper  position (exclusive) of the last byte to be requested.
      */
-    public final void endOfInterest(final long position) {
+    public final void rangeOfInterest(long lower, long upper) {
         if (channel instanceof FileCacheByteChannel) {
-            ((FileCacheByteChannel) channel).endOfInterest(position + channelOffset);
-            // Overflow is okay as value ≤ position means "read until end of stream".
+            lower = Math.addExact(lower, channelOffset);
+            upper = Math.addExact(upper, channelOffset);
+            ((FileCacheByteChannel) channel).rangeOfInterest(lower, upper);
         }
     }
 
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/FileCacheByteChannel.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/FileCacheByteChannel.java
index 5557d651ac..544532dedb 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/FileCacheByteChannel.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/FileCacheByteChannel.java
@@ -47,7 +47,7 @@ import static org.apache.sis.internal.storage.StoreUtilities.LOGGER;
  *
  * <ul>
  *   <li>Bytes read from the input stream are cached in a temporary file for making backward seeks possible.</li>
- *   <li>The number of bytes of interest {@linkplain #endOfInterest(long) can be specified}.
+ *   <li>The range of bytes of interest {@linkplain #rangeOfInterest(long, long) can be specified}.
  *       It makes possible to specify the range of bytes to download with HTTP connections.</li>
  *   <li>This implementation is thread-safe.</li>
  *   <li>Current implementation is read-only.</li>
@@ -193,7 +193,7 @@ public abstract class FileCacheByteChannel implements SeekableByteChannel {
          * @param  start  position of the first byte to read (inclusive).
          * @param  end    position of the last byte to read with the returned stream (inclusive),
          *                or {@link Long#MAX_VALUE} for end of stream.
-         * @return
+         * @return the "Range" value to put in an HTTP header.
          */
         public static String formatRange(final long start, final long end) {
             final boolean hasEnd = (end > start) && (end != Long.MAX_VALUE);
@@ -261,12 +261,13 @@ public abstract class FileCacheByteChannel implements SeekableByteChannel {
     private long position;
 
     /**
-     * Position after the last requested byte, or ≤ {@linkplain #position} if unknown.
-     * It can be used for specifying the range of bytes to download from an HTTP connection.
+     * Ranges of requested bytes, for choosing the ranges to request in new connections.
+     * Ranges are added by calls to {@link #rangeOfInterest(long, long)} and removed
+     * when the connection is created.
      *
-     * @see #endOfInterest(long)
+     * @see #rangeOfInterest(long, long)
      */
-    private long endOfInterest;
+    private final RangeSet<Long> rangesOfInterest;
 
     /**
      * Ranges of bytes in the {@linkplain #file} where data are valid.
@@ -288,6 +289,7 @@ public abstract class FileCacheByteChannel implements SeekableByteChannel {
      * @throws IOException if the temporary file cannot be created.
      */
     protected FileCacheByteChannel(final String prefix) throws IOException {
+        rangesOfInterest       = RangeSet.create(Long.class, true, false);
         rangesOfAvailableBytes = RangeSet.create(Long.class, true, false);
         file = FileChannel.open(Files.createTempFile(prefix, null),
                 StandardOpenOption.READ,
@@ -386,42 +388,79 @@ public abstract class FileCacheByteChannel implements SeekableByteChannel {
             ArgumentChecks.ensurePositive("newPosition", newPosition);
         }
         position = newPosition;
-        if (endOfInterest - newPosition < SKIP_THRESHOLD) {
-            endOfInterest = 0;      // Read until end of stream.
-        }
         return this;
     }
 
     /**
-     * Specifies the position after the last byte which is expected to be read.
-     * The number of bytes is only a hint and may be ignored, depending on subclasses.
+     * Specifies a range of bytes which is expected to be read.
+     * The range of bytes is only a hint and may be ignored, depending on subclasses.
      * Reading more bytes than specified is okay, only potentially less efficient.
-     * Values ≤ {@linkplain #position() position} means to read until the end of stream.
      *
-     * @param  end  position after the last desired byte, or a value ≤ position for reading until the end of stream.
+     * @param  lower  position (inclusive) of the first byte to be requested.
+     * @param  upper  position (exclusive) of the last byte to be requested.
      */
-    final synchronized void endOfInterest(final long end) {
-        endOfInterest = end;
+    final synchronized void rangeOfInterest(final long lower, final long upper) {
+        if (upper > lower) {
+            rangesOfInterest.add(lower, upper);
+        }
     }
 
     /**
      * Opens a connection on the range of bytes determined by the current channel position.
-     * The {@link #endOfInterest} position is considered unspecified if not greater than
-     * {@link #position} (it may be 0).
+     * The range of bytes of interest is specified in the {@link #rangesOfInterest} set.
+     * If no range is specified, this method requests all bytes until the end of stream.
+     * If some ranges are specified, this method finds the smallest "end of range" after
+     * the current position. If the gab between ranges is less than {@link #SKIP_THRESHOLD},
+     * the ranges will be merged in a single request.
      *
      * @return the opened connection (never {@code null}).
      * @throws IOException if the connection cannot be established.
      */
     private Connection openConnection() throws IOException {
-        long end = endOfInterest;
-        if (end > position) end--;      // Make inclusive.
-        else end = (length > 0) ? length-1 : Long.MAX_VALUE;
-        var c = openConnection(position, end);
+        int i = Math.max(rangesOfInterest.indexOfMin(position), 0);
+        final int size = rangesOfInterest.size();
+        long end;
+        do {                    // Should be executed exactly 1 or 2 times.
+            if (i >= size) {
+                end = (length > 0) ? length-1 : Long.MAX_VALUE;
+                break;
+            }
+            end = rangesOfInterest.getMaxLong(i) - 1;       // Inclusive
+            i++;
+        } while (end < position);
+        /*
+         * At this point we found the smallest "end of range" position.
+         * If the gab with next range is small enough, merge the ranges
+         * in order to make a single connection request.
+         */
+        while (i < size) {
+            if (rangesOfInterest.getMinLong(i) - end >= SKIP_THRESHOLD) {
+                break;
+            }
+            end = rangesOfInterest.getMaxLong(i) - 1;       // Inclusive
+            i++;
+        }
+        /*
+         * Send the HTTP or S3 request for the range of bytes.
+         * Prepare the cache file to receive those bytes.
+         * Save the stream length if it is known.
+         */
+        final Connection c = openConnection(position, end);
         file.position(c.start);
         if (c.length >= 0) {
             length = c.length;
         }
         connection = c;                 // Set only on success.
+        /*
+         * Remove the requested range from the list of ranges of interest.
+         * The range to remove is determined on the assumption that caller
+         * makes a best effort for reading bytes in sequential order, and
+         * that if the connection provides less bytes, the missing bytes
+         * will probably be requested later.
+         */
+        end = Math.min(c.end, end);
+        if (end != Long.MAX_VALUE) end++;       // Make exclusive.
+        rangesOfInterest.remove(Math.min(position, c.start), end);
         return c;
     }
 
@@ -763,6 +802,8 @@ public abstract class FileCacheByteChannel implements SeekableByteChannel {
      */
     @Override
     public synchronized String toString() {
-        return Strings.toString(getClass(), "filename", filename(), "position", position, "rangeCount", rangesOfAvailableBytes.size());
+        return Strings.toString(getClass(), "filename", filename(), "position", position,
+                "rangesOfAvailableBytes", rangesOfAvailableBytes.size(),
+                "rangesOfInterest", rangesOfInterest.size());
     }
 }
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/io/FileCacheByteChannelTest.java b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/io/FileCacheByteChannelTest.java
index fc7c8ad3c9..5da0e3dce7 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/io/FileCacheByteChannelTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/io/FileCacheByteChannelTest.java
@@ -181,7 +181,7 @@ public final class FileCacheByteChannelTest extends TestCase {
                     end = t;
                 }
                 channel.position(position);
-                channel.endOfInterest(end + 1);
+                channel.rangeOfInterest(position, end + 1);
             }
             channel.readInRandomRegion(buffer);
             while (buffer.hasRemaining()) {