//effectively map from type to (map from description to events)
static private List<EventStripe> mergeClustersToStripes(Period timeUnitLength, List<EventCluster> preMergedEvents) {
        //effectively map from type to (map from description to events)
        Map<EventType, SetMultimap< String, EventCluster>> typeMap = new HashMap<>();
        for (EventCluster aggregateEvent : preMergedEvents) {
            typeMap.computeIfAbsent(aggregateEvent.getEventType(), eventType -> HashMultimap.create())
                    .put(aggregateEvent.getDescription(), aggregateEvent);
        }
        //result list to return
        ArrayList<EventCluster> aggEvents = new ArrayList<>();
        //For each (type, description) key, merge agg events
        for (SetMultimap<String, EventCluster> descrMap : typeMap.values()) {
            //for each description ...
            for (String descr : descrMap.keySet()) {
                //run through the sorted events, merging together adjacent events
                Iterator<EventCluster> iterator = descrMap.get(descr).stream()
                        .sorted(Comparator.comparing(event -> event.getSpan().getStartMillis()))
                        .iterator();
                EventCluster current = iterator.next();
                while (iterator.hasNext()) {
                    EventCluster next = iterator.next();
                    Interval gap = current.getSpan().gap(next.getSpan());
                    //if they overlap or gap is less one quarter timeUnitLength
                    //TODO: 1/4 factor is arbitrary. review! -jm
                    if (gap == null || gap.toDuration().getMillis() <= timeUnitLength.toDurationFrom(gap.getStart()).getMillis() / 4) {
                        //merge them
                        current = EventCluster.merge(current, next);
                    } else {
                        //done merging into current, set next as new current
                        aggEvents.add(current);
                        current = next;
                    }
                }
                aggEvents.add(current);
            }
        }
        //merge clusters to stripes
        Map<ImmutablePair<EventType, String>, EventStripe> stripeDescMap = new HashMap<>();
        for (EventCluster eventCluster : aggEvents) {
            stripeDescMap.merge(ImmutablePair.of(eventCluster.getEventType(), eventCluster.getDescription()),
                    new EventStripe(eventCluster, null), EventStripe::merge);
        }
        return stripeDescMap.values().stream().sorted(Comparator.comparing(EventStripe::getStartMillis)).collect(Collectors.toList());
    }
static private List<EventStripe> mergeClustersToStripes(Period timeUnitLength, List<EventCluster> preMergedEvents) {
//effectively map from type to (map from description to events)
Map<EventType, SetMultimap< String, EventCluster>> typeMap = new HashMap<>();
for (EventCluster aggregateEvent : preMergedEvents) {
static private List<EventStripe> mergeClustersToStripes(Period timeUnitLength, List<EventCluster> preMergedEvents) {
//effectively map from type to (map from description to events)
Map<EventType, SetMultimap< String, EventCluster>> typeMap = new HashMap<>();
for (EventCluster aggregateEvent : preMergedEvents) {
typeMap.computeIfAbsent(aggregateEvent.getEventType(), eventType -> HashMultimap.create())
.put(aggregateEvent.getDescription(), aggregateEvent);
}
//result list to return
ArrayList<EventCluster> aggEvents = new ArrayList<>();
//For each (type, description) key, merge agg events
for (SetMultimap<String, EventCluster> descrMap : typeMap.values()) {
//for each description ...
static private List<EventStripe> mergeClustersToStripes(Period timeUnitLength, List<EventCluster> preMergedEvents) {
//effectively map from type to (map from description to events)
Map<EventType, SetMultimap< String, EventCluster>> typeMap = new HashMap<>();
for (EventCluster aggregateEvent : preMergedEvents) {
typeMap.computeIfAbsent(aggregateEvent.getEventType(), eventType -> HashMultimap.create())
.put(aggregateEvent.getDescription(), aggregateEvent);
}
//result list to return
ArrayList<EventCluster> aggEvents = new ArrayList<>();
//For each (type, description) key, merge agg events
for (SetMultimap<String, EventCluster> descrMap : typeMap.values()) {
//for each description ...
for (String descr : descrMap.keySet()) {
//run through the sorted events, merging together adjacent events
Iterator<EventCluster> iterator = descrMap.get(descr).stream()
.sorted(Comparator.comparing(event -> event.getSpan().getStartMillis()))
.iterator();
EventCluster current = iterator.next();
while (iterator.hasNext()) {
EventCluster next = iterator.next();
Interval gap = current.getSpan().gap(next.getSpan());
//if they overlap or gap is less one quarter timeUnitLength