docs: @Nonnull
    static List<RowSet> split(
        @Nonnull RowSet rowSet,
        @Nonnull SortedSet<ByteString> splitPoints,
        boolean preserveNullSegments) {
      // An empty RowSet represents a full table scan. Make that explicit so that there is RowRange to
      // split.
      if (RowSet.getDefaultInstance().equals(rowSet)) {
        rowSet = RowSet.newBuilder().addRowRanges(RowRange.getDefaultInstance()).build();
      }
  
      // Create sorted copies of the ranges and keys in the RowSet
      ByteString[] rowKeys =
          rowSet.getRowKeysList().toArray(new ByteString[rowSet.getRowKeysCount()]);
      RowRange[] rowRanges =
          rowSet.getRowRangesList().toArray(new RowRange[rowSet.getRowRangesCount()]);
  
      Arrays.sort(rowKeys, ByteStringComparator.INSTANCE);
      Arrays.sort(rowRanges, RANGE_START_COMPARATOR);
  
      List<RowSet> results = Lists.newArrayList();
  
      // Track consumption of input ranges & keys.
      int rowKeysStart = 0;
      int rowRangesStart = 0;
  
      // Keys and ranges that lie before the current split point,
      RowSet.Builder segment = RowSet.newBuilder();
      boolean isSegmentEmpty = true;
  
      for (ByteString splitPoint : splitPoints) {
        Preconditions.checkState(!splitPoint.isEmpty(), "Split point can't be empty");
  
        // Consume all of the row keys that lie on and to the left of the split point. Consumption is
        // designated by advancing rowKeysStart.
        for (int i = rowKeysStart; i < rowKeys.length; i++) {
          ByteString rowKey = rowKeys[i];
          if (ByteStringComparator.INSTANCE.compare(rowKey, splitPoint) <= 0) {
            segment.addRowKeys(rowKey);
            isSegmentEmpty = false;
            rowKeysStart++;
          } else {
            // This key and all following keys belong to a later segment.
            break;
          }
        }
  
        // Consume all of the ranges that lie before the split point (splitting the range if
        // necessary). Consumption is designated by advancing rowRangesStart.
        for (int i = rowRangesStart; i < rowRanges.length; i++) {
          RowRange rowRange = rowRanges[i];
  
          // Break early when encountering the first start point that is past the split point.
          // (The split point is the inclusive end of of the segment)
          int startCmp = StartPoint.extract(rowRange).compareTo(new StartPoint(splitPoint, true));
          if (startCmp > 0) {
            break;
          }
  
          // Some part of this range will be in the segment.
          isSegmentEmpty = false;
  
          // Figure out the endpoint and remainder.
          int endCmp = EndPoint.extract(rowRange).compareTo(new EndPoint(splitPoint, true));
          if (endCmp <= 0) {
            // The range is fully contained in the segment.
            segment.addRowRanges(rowRange);
  
            // Consume the range, but take care to shift partially consumed ranges to fill the gap
            // created by consuming the current range. For example if the list contained the following
            // ranges: [a-z], [b-d], [f-z] and the split point was 'e'. Then after processing the
            // split point, the list would contain: (d-z], GAP, [f-z]. So we fill the gap by shifting
            // (d-z] over by one and advancing rowRangesStart.
            // Partially consumed ranges will only exist if the original RowSet had overlapping
            // ranges, this should be a rare occurrence.
            System.arraycopy(
                rowRanges, rowRangesStart, rowRanges, rowRangesStart + 1, i - rowRangesStart);
            rowRangesStart++;
          } else {
            // The range is split:
            // Add the left part to the segment
            RowRange leftSubRange = rowRange.toBuilder().setEndKeyClosed(splitPoint).build();
            segment.addRowRanges(leftSubRange);
            // Save the remainder for the next segment. This is done by replacing the current rowRange
            // with the remainder and not advancing rowRangesStart.
            RowRange rightSubRange = rowRange.toBuilder().setStartKeyOpen(splitPoint).build();
            rowRanges[i] = rightSubRange;
          }
        }
  
        // Build the current segment
        if (!isSegmentEmpty) {
          results.add(segment.build());
          isSegmentEmpty = true;
          segment = RowSet.newBuilder();
        } else if (preserveNullSegments) {
          results.add(null);
        }
      }
  
      // Create the last segment (from the last splitKey to the end of the table)
      for (int i = rowKeysStart; i < rowKeys.length; i++) {
        isSegmentEmpty = false;
        segment.addRowKeys(rowKeys[i]);
      }
      for (int i = rowRangesStart; i < rowRanges.length; i++) {
        isSegmentEmpty = false;
        segment.addRowRanges(rowRanges[i]);
      }
      if (!isSegmentEmpty) {
        results.add(segment.build());
      } else if (preserveNullSegments) {
        results.add(null);
      }
  
      return results;
    }