GoogleCloudStorageReadChannel implements SeekableByteChannel { @Override public int read(ByteBuffer buffer) throws IOException { throwIfNotOpen(); if (buffer.remaining() == 0) { return 0; } logger.atFine().log( "Reading %s bytes at %s position from '%s'", buffer.remaining(), currentPosition, resourceId); if (currentPosition == size) { return -1; } int totalBytesRead = 0; int retriesAttempted = 0; do { int remainingBeforeRead = buffer.remaining(); performLazySeek(remainingBeforeRead); checkState( contentChannelPosition == currentPosition, "contentChannelPosition (%s) should be equal to currentPosition (%s) after lazy seek", contentChannelPosition, currentPosition); try { int numBytesRead = contentChannel.read(buffer); checkIOPrecondition(numBytesRead != 0, "Read 0 bytes without blocking"); if (numBytesRead < 0) { if (gzipEncoded) { size = currentPosition; contentChannelEnd = currentPosition; } checkIOPrecondition( currentPosition == contentChannelEnd || currentPosition == size, String.format( "Received end of stream result before all the file data has been received; " + "totalBytesRead: %d, currentPosition: %d," + " contentChannelEnd %d, size: %d, object: '%s'", totalBytesRead, currentPosition, contentChannelEnd, size, resourceId)); if (contentChannelEnd != size && currentPosition == contentChannelEnd) { closeContentChannel(); } else { break; } } if (numBytesRead > 0) { totalBytesRead += numBytesRead; currentPosition += numBytesRead; contentChannelPosition += numBytesRead; checkState( contentChannelPosition == currentPosition, "contentChannelPosition (%s) should be equal to currentPosition (%s)" + " after successful read", contentChannelPosition, currentPosition); } if (retriesAttempted != 0) { logger.atInfo().log( "Success after %s retries on reading '%s'", retriesAttempted, resourceId); } retriesAttempted = 0; } catch (IOException ioe) { logger.atFine().log( "Closing contentChannel after %s exception for '%s'.", ioe.getMessage(), resourceId); closeContentChannel(); if (buffer.remaining() != remainingBeforeRead) { int partialRead = remainingBeforeRead - buffer.remaining(); logger.atInfo().log( "Despite exception, had partial read of %s bytes from '%s'; resetting retry count.", partialRead, resourceId); retriesAttempted = 0; totalBytesRead += partialRead; currentPosition += partialRead; } if (retriesAttempted == maxRetries) { logger.atSevere().log( "Throwing exception after reaching max read retries (%s) for '%s'.", maxRetries, resourceId); throw ioe; } if (retriesAttempted == 0) { readBackOff.get().reset(); } ++retriesAttempted; logger.atWarning().withCause(ioe).log( "Failed read retry #%s/%s for '%s'. Sleeping...", retriesAttempted, maxRetries, resourceId); try { boolean backOffSuccessful = BackOffUtils.next(sleeper, readBackOff.get()); if (!backOffSuccessful) { logger.atSevere().log( "BackOff returned false; maximum total elapsed time exhausted." + " Giving up after %s/%s retries for '%s'", retriesAttempted, maxRetries, resourceId); throw ioe; } } catch (InterruptedException ie) { Thread.currentThread().interrupt(); logger.atSevere().log( "Interrupted while sleeping before retry. Giving up after %s/%s retries for '%s'", retriesAttempted, maxRetries, resourceId); ioe.addSuppressed(ie); throw ioe; } logger.atInfo().log( "Done sleeping before retry #%s/%s for '%s'", retriesAttempted, maxRetries, resourceId); } catch (RuntimeException r) { closeContentChannel(); throw r; } } while (buffer.remaining() > 0 && currentPosition < size); boolean isEndOfStream = (totalBytesRead == 0); if (isEndOfStream) { checkIOPrecondition( currentPosition == size, String.format( "Failed to read any data before all the file data has been received;" + " currentPosition: %d, size: %d, object '%s'", currentPosition, size, resourceId)); return -1; } return totalBytesRead; } GoogleCloudStorageReadChannel(
      Storage gcs,
      String bucketName,
      String objectName,
      ApiErrorExtractor errorExtractor,
      ClientRequestHelper<StorageObject> requestHelper);  GoogleCloudStorageReadChannel(
      Storage gcs,
      String bucketName,
      String objectName,
      ApiErrorExtractor errorExtractor,
      ClientRequestHelper<StorageObject> requestHelper,
      @Nonnull GoogleCloudStorageReadOptions readOptions);  GoogleCloudStorageReadChannel(
      Storage gcs,
      StorageResourceId resourceId,
      ApiErrorExtractor errorExtractor,
      ClientRequestHelper<StorageObject> requestHelper,
      @Nonnull GoogleCloudStorageReadOptions readOptions); protected  GoogleCloudStorageReadChannel(
      String bucketName, String objectName, @Nonnull GoogleCloudStorageReadOptions readOptions); void setMaxRetries(int maxRetries); @Override int read(ByteBuffer buffer); @Override SeekableByteChannel truncate(long size); @Override int write(ByteBuffer src); @Override boolean isOpen(); @Override void close(); @Override long position(); @Override SeekableByteChannel position(long newPosition); @Override long size();  }
@Test public void read_whenBufferIsEmpty() throws IOException { ByteBuffer emptyBuffer = ByteBuffer.wrap(new byte[0]); Storage storage = new Storage(HTTP_TRANSPORT, JSON_FACTORY, r -> {}); GoogleCloudStorageReadChannel readChannel = createReadChannel(storage, newLazyReadOptionsBuilder().build()); assertThat(readChannel.read(emptyBuffer)).isEqualTo(0); }