// 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.checkNotNull; import static com.google.common.base.Preconditions.checkState; 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 = checkNotNull(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(); } }