summaryrefslogtreecommitdiffstats
path: root/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/EventSorter.java
blob: 077a02787d593c2b6749d21df1a07c7dd4125f04 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// Copyright (C) 2016 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.gerrit.server.notedb.rebuild;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;

import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.SetMultimap;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.PriorityQueue;

/**
 * Helper to sort a list of events.
 *
 * <p>Events are sorted in two passes:
 *
 * <ol>
 *   <li>Sort by natural order (timestamp, patch set, author, etc.)
 *   <li>Postpone any events with dependencies to occur only after all of their dependencies, where
 *       this violates natural order.
 * </ol>
 *
 * {@link #sort()} modifies the event list in place (similar to {@link Collections#sort(List)}), but
 * does not modify any event. In particular, events might end up out of order with respect to
 * timestamp; callers are responsible for adjusting timestamps later if they prefer monotonicity.
 */
class EventSorter {
  private final List<Event> out;
  private final LinkedHashSet<Event> sorted;
  private ListMultimap<Event, Event> waiting;
  private SetMultimap<Event, Event> deps;

  EventSorter(List<Event> events) {
    LinkedHashSet<Event> all = new LinkedHashSet<>(events);
    out = events;

    for (Event e : events) {
      for (Event d : e.deps) {
        checkArgument(all.contains(d), "dep %s of %s not in input list", d, e);
      }
    }

    all.clear();
    sorted = all; // Presized.
  }

  void sort() {
    // First pass: sort by natural order.
    PriorityQueue<Event> todo = new PriorityQueue<>(out);

    // Populate waiting map after initial sort to preserve natural order.
    waiting = MultimapBuilder.hashKeys().arrayListValues().build();
    deps = MultimapBuilder.hashKeys().hashSetValues().build();
    for (Event e : todo) {
      for (Event d : e.deps) {
        deps.put(e, d);
        waiting.put(d, e);
      }
    }

    // Second pass: enforce dependencies.
    int size = out.size();
    while (!todo.isEmpty()) {
      process(todo.remove(), todo);
    }
    checkState(
        sorted.size() == size, "event sort expected %s elements, got %s", size, sorted.size());

    // Modify out in-place a la Collections#sort.
    out.clear();
    out.addAll(sorted);
  }

  void process(Event e, PriorityQueue<Event> todo) {
    if (sorted.contains(e)) {
      return; // Already emitted.
    }
    if (!deps.get(e).isEmpty()) {
      // Not all events that e depends on have been emitted yet. Ignore e for
      // now; it will get added back to the queue in the block below once its
      // last dependency is processed.
      return;
    }

    // All events that e depends on have been emitted, so e can be emitted.
    sorted.add(e);

    // Remove e from the dependency set of all events waiting on e, and add
    // those events back to the queue in the original priority order for
    // reconsideration.
    for (Event w : waiting.get(e)) {
      deps.get(w).remove(e);
      todo.add(w);
    }
  }
}