docs: private Iterable<Result<Upload>> listIncompleteUploads(final String bucketName, final String prefix,
                                                           final boolean recursive, final boolean aggregatePartSize) {
      return new Iterable<Result<Upload>>() {
        @Override
        public Iterator<Result<Upload>> iterator() {
          return new Iterator<Result<Upload>>() {
            private String nextKeyMarker;
            private String nextUploadIdMarker;
            private ListMultipartUploadsResult listMultipartUploadsResult;
            private Result<Upload> error;
            private Iterator<Upload> uploadIterator;
            private boolean completed = false;
  
            private synchronized void populate() {
              String delimiter = "/";
              if (recursive) {
                delimiter = null;
              }
  
              this.listMultipartUploadsResult = null;
              this.uploadIterator = null;
  
              try {
                this.listMultipartUploadsResult = listIncompleteUploads(bucketName, nextKeyMarker, nextUploadIdMarker,
                                                                        prefix, delimiter, 1000);
              } catch (InvalidBucketNameException | NoSuchAlgorithmException | InsufficientDataException | IOException
                       | InvalidKeyException | NoResponseException | XmlPullParserException | ErrorResponseException
                       | InternalException e) {
                this.error = new Result<>(null, e);
              } finally {
                if (this.listMultipartUploadsResult != null) {
                  this.uploadIterator = this.listMultipartUploadsResult.uploads().iterator();
                } else {
                  this.uploadIterator = new LinkedList<Upload>().iterator();
                }
              }
            }
  
            private synchronized long getAggregatedPartSize(String objectName, String uploadId)
              throws InvalidBucketNameException, NoSuchAlgorithmException, InsufficientDataException, IOException,
                     InvalidKeyException, NoResponseException, XmlPullParserException, ErrorResponseException,
                     InternalException {
              long aggregatedPartSize = 0;
  
              for (Result<Part> result : listObjectParts(bucketName, objectName, uploadId)) {
                aggregatedPartSize += result.get().partSize();
              }
  
              return aggregatedPartSize;
            }
  
            @Override
            public boolean hasNext() {
              if (this.completed) {
                return false;
              }
  
              if (this.error == null && this.uploadIterator == null) {
                populate();
              }
  
              if (this.error == null && !this.uploadIterator.hasNext()
                    && this.listMultipartUploadsResult.isTruncated()) {
                this.nextKeyMarker = this.listMultipartUploadsResult.nextKeyMarker();
                this.nextUploadIdMarker = this.listMultipartUploadsResult.nextUploadIdMarker();
                populate();
              }
  
              if (this.error != null) {
                return true;
              }
  
              if (this.uploadIterator.hasNext()) {
                return true;
              }
  
              this.completed = true;
              return false;
            }
  
            @Override
            public Result<Upload> next() {
              if (this.completed) {
                throw new NoSuchElementException();
              }
  
              if (this.error == null && this.uploadIterator == null) {
                populate();
              }
  
              if (this.error == null && !this.uploadIterator.hasNext()
                    && this.listMultipartUploadsResult.isTruncated()) {
                this.nextKeyMarker = this.listMultipartUploadsResult.nextKeyMarker();
                this.nextUploadIdMarker = this.listMultipartUploadsResult.nextUploadIdMarker();
                populate();
              }
  
              if (this.error != null) {
                this.completed = true;
                return this.error;
              }
  
              if (this.uploadIterator.hasNext()) {
                Upload upload = this.uploadIterator.next();
  
                if (aggregatePartSize) {
                  long aggregatedPartSize;
  
                  try {
                    aggregatedPartSize = getAggregatedPartSize(upload.objectName(), upload.uploadId());
                  } catch (InvalidBucketNameException | NoSuchAlgorithmException | InsufficientDataException | IOException
                           | InvalidKeyException | NoResponseException | XmlPullParserException | ErrorResponseException
                           | InternalException e) {
                    // special case: ignore the error as we can't propagate the exception in next()
                    aggregatedPartSize = -1;
                  }
  
                  upload.setAggregatedPartSize(aggregatedPartSize);
                }
  
                return new Result<>(upload, null);
              }
  
              this.completed = true;
              throw new NoSuchElementException();
            }
  
            @Override
            public void remove() {
              throw new UnsupportedOperationException();
            }
          };
        }
      };
    }