private List<Part> calculateSplitParts(final FeedKey feedKey,
                                           final FileGroup fileGroup,
                                           final AggregatorConfig aggregatorConfig) throws IOException {
        // Determine if we need to split this data into parts.
        final List<Part> parts = new ArrayList<>();
        AggregateState aggregateState = getOrCreateAggregateState(feedKey, aggregatorConfig);

        // Calculate where we might want to split the incoming data.
        final long maxItemsPerAggregate = aggregatorConfig.getMaxItemsPerAggregate();
        final long maxUncompressedByteSize = aggregatorConfig.getMaxUncompressedByteSize();
        long currentAggregateItemCount = aggregateState.itemCount;
        long currentAggregateBytes = aggregateState.totalBytes;
        long partItems = 0;
        long partBytes = 0;
        final List<ZipEntryGroup> partEntries = new ArrayList<>();
        boolean firstEntry = true;
        // Intern the feedKeys in the entries to reduce mem use
        final FeedKeyInterner feedKeyInterner = FeedKey.createInterner();
        feedKeyInterner.intern(feedKey);

        try (final BufferedReader bufferedReader = Files.newBufferedReader(fileGroup.getEntries())) {
            String line = bufferedReader.readLine();
            while (line != null) {
                final ZipEntryGroup zipEntryGroup = ZipEntryGroup.read(line, feedKeyInterner);
                final long totalUncompressedSize = zipEntryGroup.getTotalUncompressedSize();

                // If the current aggregate has items then we might want to close and start a new one.
                if (currentAggregateItemCount > 0 &&
                    (currentAggregateItemCount + 1 > maxItemsPerAggregate ||
                     currentAggregateBytes + totalUncompressedSize > maxUncompressedByteSize)) {
                    if (firstEntry) {
                        // If the first entry immediately causes the current aggregate to exceed the required bounds
                        // then close it and create a new one.

                        // Close the current aggregate.
                        closeAggregate(feedKey, aggregateState);

                        // Create a new aggregate.
                        aggregateState = getOrCreateAggregateState(feedKey, aggregatorConfig);
                    } else {
                        // Split. Copy the list as the source is about to be cleared
                        final Part part = new Part(partItems, partBytes, List.copyOf(partEntries));
                        parts.add(part);
                    }

                    // Reset.
                    currentAggregateItemCount = 0;
                    currentAggregateBytes = 0;
                    partItems = 0;
                    partBytes = 0;
                    partEntries.clear();
                }

                // Add to the aggregate.
                currentAggregateItemCount++;
                currentAggregateBytes += totalUncompressedSize;
                partItems++;
                partBytes += totalUncompressedSize;
                partEntries.add(zipEntryGroup);

                line = bufferedReader.readLine();
                firstEntry = false;
            }
            // Add final part.
            parts.add(new Part(partItems, partBytes, List.copyOf(partEntries)));
        }
        return parts;
    }
Calculate the number of logical parts the source zip will need to be split into
in order to fit output aggregates without them exceeding the size and item
count constraints.

<p>
MUST be called under feedKeyLock.
</p>

@param feedKey          The feed
@param fileGroup        the file group to examine.
@param aggregatorConfig
@return A list of parts to split the zip data by.
@throws IOException Could be throws when reading entries.
stroom-proxy/stroom-proxy-app/src/main/java/stroom/proxy/app/handler/PreAggregator.java