docs: private <RequestT, ResponseT> ResponseT uploadObject(final InputStream originalStream,
                                                           final File file,
                                                           final ObjectMetadata metadata,
                                                           final ProgressListener listener,
                                                           final Request<RequestT> request,
                                                           final S3DataSource originalRequest,
                                                           final boolean skipServerSideValidation,
                                                           final boolean skipClientSideValidationPerRequest,
                                                           final UploadObjectStrategy<RequestT, ResponseT> uploadStrategy,
                                                           final boolean setContentTypeIfNotProvided) {
  
          InputStream input = getInputStream(originalStream, file, metadata, request,
                                             skipServerSideValidation, setContentTypeIfNotProvided);
  
          final ObjectMetadata returnedMetadata;
          MD5DigestCalculatingInputStream md5DigestStream = null;
          try {
              if (metadata.getContentMD5() == null && !skipClientSideValidationPerRequest) {
                  /*
                   * If the user hasn't set the content MD5, then we don't want to buffer the whole
                   * stream in memory just to calculate it. Instead, we can calculate it on the fly
                   * and validate it with the returned ETag from the object upload.
                   */
                  input = md5DigestStream = new MD5DigestCalculatingInputStream(input);
              }
  
              populateRequestMetadata(request, metadata);
              request.setContent(input);
              publishProgress(listener, ProgressEventType.TRANSFER_STARTED_EVENT);
              try {
                  returnedMetadata = uploadStrategy.invokeServiceCall(request);
              } catch (Throwable t) {
                  publishProgress(listener, ProgressEventType.TRANSFER_FAILED_EVENT);
                  throw failure(t);
              }
          } finally {
              cleanupDataSource(originalRequest, file, originalStream, input, log);
          }
  
          String contentMd5 = metadata.getContentMD5();
          if (md5DigestStream != null) {
              contentMd5 = Base64.encodeAsString(md5DigestStream.getMd5Digest());
          }
  
          final String etag = returnedMetadata.getETag();
          if (contentMd5 != null && !skipMd5CheckStrategy.skipClientSideValidationPerPutResponse(returnedMetadata)) {
              byte[] clientSideHash = BinaryUtils.fromBase64(contentMd5);
              byte[] serverSideHash = BinaryUtils.fromHex(etag);
  
              if (!Arrays.equals(clientSideHash, serverSideHash)) {
                  publishProgress(listener, ProgressEventType.TRANSFER_FAILED_EVENT);
                  throw new SdkClientException(
                      "Unable to verify integrity of data upload. Client calculated content hash (contentMD5: " + contentMd5
                      + " in base 64) didn't match hash (etag: " + etag + " in hex) calculated by Amazon S3.  "
                      + "You may need to delete the data stored in Amazon S3. (metadata.contentMD5: " + metadata.getContentMD5()
                      + ", md5DigestStream: " + md5DigestStream
                      + uploadStrategy.md5ValidationErrorSuffix()
                      + ")");
              }
          }
  
          publishProgress(listener, ProgressEventType.TRANSFER_COMPLETED_EVENT);
  
          return uploadStrategy.createResult(returnedMetadata, contentMd5);
      }