| // 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 static java.util.Objects.requireNonNull; |
| |
| import com.google.common.collect.Lists; |
| import com.google.gerrit.reviewdb.client.Account; |
| import com.google.gerrit.reviewdb.client.PatchSet; |
| import java.sql.Timestamp; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.Objects; |
| |
| class EventList<E extends Event> implements Iterable<E> { |
| private final ArrayList<E> list = new ArrayList<>(); |
| private boolean isSubmit; |
| |
| @Override |
| public Iterator<E> iterator() { |
| return list.iterator(); |
| } |
| |
| void add(E e) { |
| list.add(e); |
| if (e.isSubmit()) { |
| isSubmit = true; |
| } |
| } |
| |
| void clear() { |
| list.clear(); |
| isSubmit = false; |
| } |
| |
| boolean isEmpty() { |
| return list.isEmpty(); |
| } |
| |
| boolean canAdd(E e) { |
| if (isEmpty()) { |
| return true; |
| } |
| if (e instanceof FinalUpdatesEvent) { |
| return false; // FinalUpdatesEvent always gets its own update. |
| } |
| |
| Event last = getLast(); |
| if (!Objects.equals(e.user, last.user) |
| || !Objects.equals(e.realUser, last.realUser) |
| || !e.psId.equals(last.psId)) { |
| return false; // Different patch set or author. |
| } |
| if (e.canHaveTag() && canHaveTag() && !Objects.equals(e.tag, getTag())) { |
| // We should trust the tag field, and it doesn't match. |
| return false; |
| } |
| if (e.isPostSubmitApproval() && isSubmit) { |
| // Post-submit approvals must come after the update that submits. |
| return false; |
| } |
| |
| long t = e.when.getTime(); |
| long tFirst = getFirstTime(); |
| long tLast = getLastTime(); |
| checkArgument(t >= tLast, "event %s is before previous event in list %s", e, last); |
| if (t - tLast > ChangeRebuilderImpl.MAX_DELTA_MS |
| || t - tFirst > ChangeRebuilderImpl.MAX_WINDOW_MS) { |
| return false; // Too much time elapsed. |
| } |
| |
| if (!e.uniquePerUpdate()) { |
| return true; |
| } |
| for (Event o : this) { |
| if (e.getClass() == o.getClass()) { |
| return false; // Only one event of this type allowed per update. |
| } |
| } |
| |
| // TODO(dborowitz): Additional heuristics, like keeping events separate if |
| // they affect overlapping fields within a single entity. |
| |
| return true; |
| } |
| |
| Timestamp getWhen() { |
| return get(0).when; |
| } |
| |
| PatchSet.Id getPatchSetId() { |
| PatchSet.Id id = requireNonNull(get(0).psId); |
| for (int i = 1; i < size(); i++) { |
| checkState( |
| get(i).psId.equals(id), "mismatched patch sets in EventList: %s != %s", id, get(i).psId); |
| } |
| return id; |
| } |
| |
| Account.Id getAccountId() { |
| Account.Id id = get(0).user; |
| for (int i = 1; i < size(); i++) { |
| checkState( |
| Objects.equals(id, get(i).user), |
| "mismatched users in EventList: %s != %s", |
| id, |
| get(i).user); |
| } |
| return id; |
| } |
| |
| Account.Id getRealAccountId() { |
| Account.Id id = get(0).realUser; |
| for (int i = 1; i < size(); i++) { |
| checkState( |
| Objects.equals(id, get(i).realUser), |
| "mismatched real users in EventList: %s != %s", |
| id, |
| get(i).realUser); |
| } |
| return id; |
| } |
| |
| String getTag() { |
| for (E e : Lists.reverse(list)) { |
| if (e.tag != null) { |
| return e.tag; |
| } |
| } |
| return null; |
| } |
| |
| private boolean canHaveTag() { |
| return list.stream().anyMatch(Event::canHaveTag); |
| } |
| |
| private E get(int i) { |
| return list.get(i); |
| } |
| |
| private int size() { |
| return list.size(); |
| } |
| |
| private E getLast() { |
| return list.get(list.size() - 1); |
| } |
| |
| private long getLastTime() { |
| return getLast().when.getTime(); |
| } |
| |
| private long getFirstTime() { |
| return list.get(0).when.getTime(); |
| } |
| } |