LCOV - code coverage report
Current view: top level - server/notedb - ChangeNotesState.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 340 355 95.8 %
Date: 2022-11-19 15:00:39 Functions: 52 54 96.3 %

          Line data    Source code
       1             : // Copyright (C) 2016 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.server.notedb;
      16             : 
      17             : import static com.google.common.base.Preconditions.checkArgument;
      18             : import static com.google.common.base.Preconditions.checkState;
      19             : import static com.google.common.collect.ImmutableList.toImmutableList;
      20             : import static com.google.common.collect.ImmutableListMultimap.toImmutableListMultimap;
      21             : import static java.util.Objects.requireNonNull;
      22             : 
      23             : import com.google.auto.value.AutoValue;
      24             : import com.google.common.annotations.VisibleForTesting;
      25             : import com.google.common.base.Converter;
      26             : import com.google.common.base.Enums;
      27             : import com.google.common.base.Strings;
      28             : import com.google.common.collect.ImmutableList;
      29             : import com.google.common.collect.ImmutableListMultimap;
      30             : import com.google.common.collect.ImmutableSet;
      31             : import com.google.common.collect.ImmutableTable;
      32             : import com.google.common.collect.ListMultimap;
      33             : import com.google.common.collect.Maps;
      34             : import com.google.common.collect.Table;
      35             : import com.google.gerrit.common.Nullable;
      36             : import com.google.gerrit.entities.Account;
      37             : import com.google.gerrit.entities.Address;
      38             : import com.google.gerrit.entities.AttentionSetUpdate;
      39             : import com.google.gerrit.entities.BranchNameKey;
      40             : import com.google.gerrit.entities.Change;
      41             : import com.google.gerrit.entities.ChangeMessage;
      42             : import com.google.gerrit.entities.HumanComment;
      43             : import com.google.gerrit.entities.PatchSet;
      44             : import com.google.gerrit.entities.PatchSetApproval;
      45             : import com.google.gerrit.entities.Project;
      46             : import com.google.gerrit.entities.SubmitRecord;
      47             : import com.google.gerrit.entities.SubmitRequirementResult;
      48             : import com.google.gerrit.entities.converter.ChangeMessageProtoConverter;
      49             : import com.google.gerrit.entities.converter.PatchSetApprovalProtoConverter;
      50             : import com.google.gerrit.entities.converter.PatchSetProtoConverter;
      51             : import com.google.gerrit.json.OutputFormat;
      52             : import com.google.gerrit.proto.Protos;
      53             : import com.google.gerrit.server.AssigneeStatusUpdate;
      54             : import com.google.gerrit.server.ReviewerByEmailSet;
      55             : import com.google.gerrit.server.ReviewerSet;
      56             : import com.google.gerrit.server.ReviewerStatusUpdate;
      57             : import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto;
      58             : import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.AssigneeStatusUpdateProto;
      59             : import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.AttentionSetUpdateProto;
      60             : import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ChangeColumnsProto;
      61             : import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerByEmailSetEntryProto;
      62             : import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerSetEntryProto;
      63             : import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerStatusUpdateProto;
      64             : import com.google.gerrit.server.cache.serialize.CacheSerializer;
      65             : import com.google.gerrit.server.cache.serialize.ObjectIdConverter;
      66             : import com.google.gerrit.server.index.change.ChangeField.StoredSubmitRecord;
      67             : import com.google.gson.Gson;
      68             : import java.time.Instant;
      69             : import java.util.List;
      70             : import java.util.Map;
      71             : import java.util.Optional;
      72             : import java.util.Set;
      73             : import org.eclipse.jgit.lib.ObjectId;
      74             : 
      75             : /**
      76             :  * Immutable state associated with a change meta ref at a given commit.
      77             :  *
      78             :  * <p>One instance is the output of a single {@link ChangeNotesParser}, and contains types required
      79             :  * to support public methods on {@link ChangeNotes}. It is intended to be cached in-process.
      80             :  *
      81             :  * <p>When new fields are added to the {@link ChangeNotesState}, {@link
      82             :  * ChangeNotesCache.Weigher#weigh} should be updated.
      83             :  *
      84             :  * <p>Note that {@link ChangeNotes} contains more than just a single {@code ChangeNoteState}, such
      85             :  * as per-draft information, so that class is not cached directly.
      86             :  */
      87             : // TODO(paiking): This class should be refactored to get rid of potentially duplicate or unneeded
      88             : // variables, such as allAttentionSetUpdates, reviewerUpdates, and others.
      89             : 
      90             : @AutoValue
      91         103 : public abstract class ChangeNotesState {
      92             : 
      93             :   static ChangeNotesState empty(Change change) {
      94         103 :     return Builder.empty(change.getId()).build();
      95             :   }
      96             : 
      97             :   private static Builder builder() {
      98         103 :     return new AutoValue_ChangeNotesState.Builder();
      99             :   }
     100             : 
     101             :   static ChangeNotesState create(
     102             :       ObjectId metaId,
     103             :       Change.Id changeId,
     104             :       Change.Key changeKey,
     105             :       Instant createdOn,
     106             :       Instant lastUpdatedOn,
     107             :       Account.Id owner,
     108             :       String serverId,
     109             :       String branch,
     110             :       @Nullable PatchSet.Id currentPatchSetId,
     111             :       String subject,
     112             :       @Nullable String topic,
     113             :       @Nullable String originalSubject,
     114             :       @Nullable String submissionId,
     115             :       @Nullable Change.Status status,
     116             :       Set<String> hashtags,
     117             :       Map<PatchSet.Id, PatchSet> patchSets,
     118             :       ListMultimap<PatchSet.Id, PatchSetApproval> approvals,
     119             :       ReviewerSet reviewers,
     120             :       ReviewerByEmailSet reviewersByEmail,
     121             :       ReviewerSet pendingReviewers,
     122             :       ReviewerByEmailSet pendingReviewersByEmail,
     123             :       List<Account.Id> allPastReviewers,
     124             :       List<ReviewerStatusUpdate> reviewerUpdates,
     125             :       Set<AttentionSetUpdate> attentionSetUpdates,
     126             :       List<AttentionSetUpdate> allAttentionSetUpdates,
     127             :       List<AssigneeStatusUpdate> assigneeUpdates,
     128             :       List<SubmitRecord> submitRecords,
     129             :       List<ChangeMessage> changeMessages,
     130             :       ListMultimap<ObjectId, HumanComment> publishedComments,
     131             :       List<SubmitRequirementResult> submitRequirementResults,
     132             :       boolean isPrivate,
     133             :       boolean workInProgress,
     134             :       boolean reviewStarted,
     135             :       @Nullable Change.Id revertOf,
     136             :       @Nullable PatchSet.Id cherryPickOf,
     137             :       int updateCount,
     138             :       @Nullable Instant mergedOn) {
     139         103 :     requireNonNull(
     140             :         metaId,
     141             :         () ->
     142           0 :             String.format(
     143             :                 "metaId is required when passing arguments to create(...)."
     144             :                     + " To create an empty %s without"
     145             :                     + " NoteDb data, use empty(...) instead",
     146           0 :                 ChangeNotesState.class.getSimpleName()));
     147         103 :     return builder()
     148         103 :         .metaId(metaId)
     149         103 :         .changeId(changeId)
     150         103 :         .columns(
     151         103 :             ChangeColumns.builder()
     152         103 :                 .changeKey(changeKey)
     153         103 :                 .createdOn(createdOn)
     154         103 :                 .lastUpdatedOn(lastUpdatedOn)
     155         103 :                 .owner(owner)
     156         103 :                 .branch(branch)
     157         103 :                 .status(status)
     158         103 :                 .currentPatchSetId(currentPatchSetId)
     159         103 :                 .subject(subject)
     160         103 :                 .topic(topic)
     161         103 :                 .originalSubject(originalSubject)
     162         103 :                 .submissionId(submissionId)
     163         103 :                 .isPrivate(isPrivate)
     164         103 :                 .workInProgress(workInProgress)
     165         103 :                 .reviewStarted(reviewStarted)
     166         103 :                 .revertOf(revertOf)
     167         103 :                 .cherryPickOf(cherryPickOf)
     168         103 :                 .build())
     169         103 :         .hashtags(hashtags)
     170         103 :         .serverId(serverId)
     171         103 :         .patchSets(patchSets.entrySet())
     172         103 :         .approvals(approvals.entries())
     173         103 :         .reviewers(reviewers)
     174         103 :         .reviewersByEmail(reviewersByEmail)
     175         103 :         .pendingReviewers(pendingReviewers)
     176         103 :         .pendingReviewersByEmail(pendingReviewersByEmail)
     177         103 :         .allPastReviewers(allPastReviewers)
     178         103 :         .reviewerUpdates(reviewerUpdates)
     179         103 :         .attentionSet(attentionSetUpdates)
     180         103 :         .allAttentionSetUpdates(allAttentionSetUpdates)
     181         103 :         .assigneeUpdates(assigneeUpdates)
     182         103 :         .submitRecords(submitRecords)
     183         103 :         .changeMessages(changeMessages)
     184         103 :         .publishedComments(publishedComments)
     185         103 :         .submitRequirementsResult(submitRequirementResults)
     186         103 :         .updateCount(updateCount)
     187         103 :         .mergedOn(mergedOn)
     188         103 :         .build();
     189             :   }
     190             : 
     191             :   /**
     192             :    * Subset of Change columns that can be represented in NoteDb.
     193             :    *
     194             :    * <p>Fields should match the column names in {@link Change}, and are in listed column order.
     195             :    */
     196             :   @AutoValue
     197         103 :   abstract static class ChangeColumns {
     198             : 
     199             :     static Builder builder() {
     200         103 :       return new AutoValue_ChangeNotesState_ChangeColumns.Builder();
     201             :     }
     202             : 
     203             :     abstract Change.Key changeKey();
     204             : 
     205             :     abstract Instant createdOn();
     206             : 
     207             :     abstract Instant lastUpdatedOn();
     208             : 
     209             :     abstract Account.Id owner();
     210             : 
     211             :     // Project not included, as it's not stored anywhere in the meta ref.
     212             :     abstract String branch();
     213             : 
     214             :     // TODO(dborowitz): Use a sensible default other than null
     215             :     @Nullable
     216             :     abstract Change.Status status();
     217             : 
     218             :     @Nullable
     219             :     abstract PatchSet.Id currentPatchSetId();
     220             : 
     221             :     abstract String subject();
     222             : 
     223             :     @Nullable
     224             :     abstract String topic();
     225             : 
     226             :     @Nullable
     227             :     abstract String originalSubject();
     228             : 
     229             :     @Nullable
     230             :     abstract String submissionId();
     231             : 
     232             :     abstract boolean isPrivate();
     233             : 
     234             :     abstract boolean workInProgress();
     235             : 
     236             :     abstract boolean reviewStarted();
     237             : 
     238             :     @Nullable
     239             :     abstract Change.Id revertOf();
     240             : 
     241             :     @Nullable
     242             :     abstract PatchSet.Id cherryPickOf();
     243             : 
     244             :     abstract Builder toBuilder();
     245             : 
     246             :     @AutoValue.Builder
     247         103 :     abstract static class Builder {
     248             : 
     249             :       abstract Builder changeKey(Change.Key changeKey);
     250             : 
     251             :       abstract Builder createdOn(Instant createdOn);
     252             : 
     253             :       abstract Builder lastUpdatedOn(Instant lastUpdatedOn);
     254             : 
     255             :       abstract Builder owner(Account.Id owner);
     256             : 
     257             :       abstract Builder branch(String branch);
     258             : 
     259             :       abstract Builder currentPatchSetId(@Nullable PatchSet.Id currentPatchSetId);
     260             : 
     261             :       abstract Builder subject(String subject);
     262             : 
     263             :       abstract Builder topic(@Nullable String topic);
     264             : 
     265             :       abstract Builder originalSubject(@Nullable String originalSubject);
     266             : 
     267             :       abstract Builder submissionId(@Nullable String submissionId);
     268             : 
     269             :       abstract Builder status(@Nullable Change.Status status);
     270             : 
     271             :       abstract Builder isPrivate(boolean isPrivate);
     272             : 
     273             :       abstract Builder workInProgress(boolean workInProgress);
     274             : 
     275             :       abstract Builder reviewStarted(boolean reviewStarted);
     276             : 
     277             :       abstract Builder revertOf(@Nullable Change.Id revertOf);
     278             : 
     279             :       abstract Builder cherryPickOf(@Nullable PatchSet.Id cherryPickOf);
     280             : 
     281             :       abstract ChangeColumns build();
     282             :     }
     283             :   }
     284             : 
     285             :   // Only null if NoteDb is disabled.
     286             :   @Nullable
     287             :   abstract ObjectId metaId();
     288             : 
     289             :   abstract Change.Id changeId();
     290             : 
     291             :   // Only null if NoteDb is disabled.
     292             :   @Nullable
     293             :   abstract ChangeColumns columns();
     294             : 
     295             :   // Other related to this Change.
     296             :   abstract ImmutableSet<String> hashtags();
     297             : 
     298             :   @Nullable
     299             :   abstract String serverId();
     300             : 
     301             :   abstract ImmutableList<Map.Entry<PatchSet.Id, PatchSet>> patchSets();
     302             : 
     303             :   abstract ImmutableList<Map.Entry<PatchSet.Id, PatchSetApproval>> approvals();
     304             : 
     305             :   abstract ReviewerSet reviewers();
     306             : 
     307             :   abstract ReviewerByEmailSet reviewersByEmail();
     308             : 
     309             :   abstract ReviewerSet pendingReviewers();
     310             : 
     311             :   abstract ReviewerByEmailSet pendingReviewersByEmail();
     312             : 
     313             :   abstract ImmutableList<Account.Id> allPastReviewers();
     314             : 
     315             :   abstract ImmutableList<ReviewerStatusUpdate> reviewerUpdates();
     316             : 
     317             :   /** Returns the most recent update (i.e. current status) per user. */
     318             :   abstract ImmutableSet<AttentionSetUpdate> attentionSet();
     319             : 
     320             :   /** Returns all attention set updates. */
     321             :   abstract ImmutableList<AttentionSetUpdate> allAttentionSetUpdates();
     322             : 
     323             :   abstract ImmutableList<AssigneeStatusUpdate> assigneeUpdates();
     324             : 
     325             :   abstract ImmutableList<SubmitRecord> submitRecords();
     326             : 
     327             :   abstract ImmutableList<ChangeMessage> changeMessages();
     328             : 
     329             :   abstract ImmutableListMultimap<ObjectId, HumanComment> publishedComments();
     330             : 
     331             :   abstract ImmutableList<SubmitRequirementResult> submitRequirementsResult();
     332             : 
     333             :   abstract int updateCount();
     334             : 
     335             :   @Nullable
     336             :   abstract Instant mergedOn();
     337             : 
     338             :   Change newChange(Project.NameKey project) {
     339           0 :     ChangeColumns c = requireNonNull(columns(), "columns are required");
     340           0 :     Change change =
     341             :         new Change(
     342           0 :             c.changeKey(),
     343           0 :             changeId(),
     344           0 :             c.owner(),
     345           0 :             BranchNameKey.create(project, c.branch()),
     346           0 :             c.createdOn());
     347           0 :     copyNonConstructorColumnsTo(change);
     348           0 :     return change;
     349             :   }
     350             : 
     351             :   void copyColumnsTo(Change change) {
     352         103 :     ChangeColumns c = columns();
     353         103 :     checkState(
     354         103 :         c != null && metaId() != null,
     355             :         "missing columns or metaId in ChangeNotesState; is NoteDb enabled? %s",
     356             :         this);
     357         103 :     change.setKey(c.changeKey());
     358         103 :     change.setOwner(c.owner());
     359         103 :     change.setDest(BranchNameKey.create(change.getProject(), c.branch()));
     360         103 :     change.setCreatedOn(c.createdOn());
     361         103 :     copyNonConstructorColumnsTo(change);
     362         103 :   }
     363             : 
     364             :   private void copyNonConstructorColumnsTo(Change change) {
     365         103 :     ChangeColumns c = requireNonNull(columns(), "columns are required");
     366         103 :     if (c.status() != null) {
     367         103 :       change.setStatus(c.status());
     368             :     }
     369         103 :     change.setTopic(Strings.emptyToNull(c.topic()));
     370         103 :     change.setLastUpdatedOn(c.lastUpdatedOn());
     371         103 :     change.setSubmissionId(c.submissionId());
     372         103 :     if (!assigneeUpdates().isEmpty()) {
     373           7 :       change.setAssignee(assigneeUpdates().get(0).currentAssignee().orElse(null));
     374             :     }
     375         103 :     change.setPrivate(c.isPrivate());
     376         103 :     change.setWorkInProgress(c.workInProgress());
     377         103 :     change.setReviewStarted(c.reviewStarted());
     378         103 :     change.setRevertOf(c.revertOf());
     379         103 :     change.setCherryPickOf(c.cherryPickOf());
     380             : 
     381         103 :     if (!patchSets().isEmpty()) {
     382         103 :       change.setCurrentPatchSet(c.currentPatchSetId(), c.subject(), c.originalSubject());
     383             :     } else {
     384             :       // TODO(dborowitz): This should be an error, but for now it's required for
     385             :       // some tests to pass.
     386           0 :       change.clearCurrentPatchSet();
     387             :     }
     388         103 :   }
     389             : 
     390             :   @AutoValue.Builder
     391         103 :   abstract static class Builder {
     392             : 
     393             :     static Builder empty(Change.Id changeId) {
     394         103 :       return new AutoValue_ChangeNotesState.Builder()
     395         103 :           .changeId(changeId)
     396         103 :           .hashtags(ImmutableSet.of())
     397         103 :           .patchSets(ImmutableList.of())
     398         103 :           .approvals(ImmutableList.of())
     399         103 :           .reviewers(ReviewerSet.empty())
     400         103 :           .reviewersByEmail(ReviewerByEmailSet.empty())
     401         103 :           .pendingReviewers(ReviewerSet.empty())
     402         103 :           .pendingReviewersByEmail(ReviewerByEmailSet.empty())
     403         103 :           .allPastReviewers(ImmutableList.of())
     404         103 :           .reviewerUpdates(ImmutableList.of())
     405         103 :           .attentionSet(ImmutableSet.of())
     406         103 :           .allAttentionSetUpdates(ImmutableList.of())
     407         103 :           .assigneeUpdates(ImmutableList.of())
     408         103 :           .submitRecords(ImmutableList.of())
     409         103 :           .changeMessages(ImmutableList.of())
     410         103 :           .publishedComments(ImmutableListMultimap.of())
     411         103 :           .submitRequirementsResult(ImmutableList.of())
     412         103 :           .updateCount(0);
     413             :     }
     414             : 
     415             :     abstract Builder metaId(ObjectId metaId);
     416             : 
     417             :     abstract Builder changeId(Change.Id changeId);
     418             : 
     419             :     abstract Builder columns(ChangeColumns columns);
     420             : 
     421             :     abstract Builder serverId(String serverId);
     422             : 
     423             :     abstract Builder hashtags(Iterable<String> hashtags);
     424             : 
     425             :     abstract Builder patchSets(Iterable<Map.Entry<PatchSet.Id, PatchSet>> patchSets);
     426             : 
     427             :     abstract Builder approvals(Iterable<Map.Entry<PatchSet.Id, PatchSetApproval>> approvals);
     428             : 
     429             :     abstract Builder reviewers(ReviewerSet reviewers);
     430             : 
     431             :     abstract Builder reviewersByEmail(ReviewerByEmailSet reviewersByEmail);
     432             : 
     433             :     abstract Builder pendingReviewers(ReviewerSet pendingReviewers);
     434             : 
     435             :     abstract Builder pendingReviewersByEmail(ReviewerByEmailSet pendingReviewersByEmail);
     436             : 
     437             :     abstract Builder allPastReviewers(List<Account.Id> allPastReviewers);
     438             : 
     439             :     abstract Builder reviewerUpdates(List<ReviewerStatusUpdate> reviewerUpdates);
     440             : 
     441             :     abstract Builder attentionSet(Set<AttentionSetUpdate> attentionSetUpdates);
     442             : 
     443             :     abstract Builder allAttentionSetUpdates(List<AttentionSetUpdate> attentionSetUpdates);
     444             : 
     445             :     abstract Builder assigneeUpdates(List<AssigneeStatusUpdate> assigneeUpdates);
     446             : 
     447             :     abstract Builder submitRecords(List<SubmitRecord> submitRecords);
     448             : 
     449             :     abstract Builder changeMessages(List<ChangeMessage> changeMessages);
     450             : 
     451             :     abstract Builder publishedComments(ListMultimap<ObjectId, HumanComment> publishedComments);
     452             : 
     453             :     abstract Builder submitRequirementsResult(
     454             :         List<SubmitRequirementResult> submitRequirementsResult);
     455             : 
     456             :     abstract Builder updateCount(int updateCount);
     457             : 
     458             :     abstract Builder mergedOn(Instant mergedOn);
     459             : 
     460             :     abstract ChangeNotesState build();
     461             :   }
     462             : 
     463             :   /**
     464             :    * Convert ChangeNotesState (which is AutoValue based) to byte[] and back, using protocol buffers.
     465             :    */
     466         152 :   enum Serializer implements CacheSerializer<ChangeNotesState> {
     467         152 :     INSTANCE;
     468             : 
     469         152 :     @VisibleForTesting static final Gson GSON = OutputFormat.JSON_COMPACT.newGson();
     470             : 
     471         152 :     private static final Converter<String, Change.Status> STATUS_CONVERTER =
     472         152 :         Enums.stringConverter(Change.Status.class);
     473         152 :     private static final Converter<String, ReviewerStateInternal> REVIEWER_STATE_CONVERTER =
     474         152 :         Enums.stringConverter(ReviewerStateInternal.class);
     475             : 
     476             :     @Override
     477             :     public byte[] serialize(ChangeNotesState object) {
     478           1 :       checkArgument(object.metaId() != null, "meta ID is required in: %s", object);
     479           1 :       checkArgument(object.columns() != null, "ChangeColumns is required in: %s", object);
     480           1 :       ChangeNotesStateProto.Builder b = ChangeNotesStateProto.newBuilder();
     481             : 
     482           1 :       b.setMetaId(ObjectIdConverter.create().toByteString(object.metaId()))
     483           1 :           .setChangeId(object.changeId().get())
     484           1 :           .setColumns(toChangeColumnsProto(object.columns()));
     485             : 
     486           1 :       if (object.serverId() != null) {
     487           1 :         b.setServerId(object.serverId());
     488           1 :         b.setHasServerId(true);
     489             :       }
     490           1 :       object.hashtags().forEach(b::addHashtag);
     491           1 :       object
     492           1 :           .patchSets()
     493           1 :           .forEach(e -> b.addPatchSet(PatchSetProtoConverter.INSTANCE.toProto(e.getValue())));
     494           1 :       object
     495           1 :           .approvals()
     496           1 :           .forEach(
     497           1 :               e -> b.addApproval(PatchSetApprovalProtoConverter.INSTANCE.toProto(e.getValue())));
     498             : 
     499           1 :       object.reviewers().asTable().cellSet().forEach(c -> b.addReviewer(toReviewerSetEntry(c)));
     500           1 :       object
     501           1 :           .reviewersByEmail()
     502           1 :           .asTable()
     503           1 :           .cellSet()
     504           1 :           .forEach(c -> b.addReviewerByEmail(toReviewerByEmailSetEntry(c)));
     505           1 :       object
     506           1 :           .pendingReviewers()
     507           1 :           .asTable()
     508           1 :           .cellSet()
     509           1 :           .forEach(c -> b.addPendingReviewer(toReviewerSetEntry(c)));
     510           1 :       object
     511           1 :           .pendingReviewersByEmail()
     512           1 :           .asTable()
     513           1 :           .cellSet()
     514           1 :           .forEach(c -> b.addPendingReviewerByEmail(toReviewerByEmailSetEntry(c)));
     515             : 
     516           1 :       object.allPastReviewers().forEach(a -> b.addPastReviewer(a.get()));
     517           1 :       object.reviewerUpdates().forEach(u -> b.addReviewerUpdate(toReviewerStatusUpdateProto(u)));
     518           1 :       object.attentionSet().forEach(u -> b.addAttentionSetUpdate(toAttentionSetUpdateProto(u)));
     519           1 :       object
     520           1 :           .allAttentionSetUpdates()
     521           1 :           .forEach(u -> b.addAllAttentionSetUpdate(toAttentionSetUpdateProto(u)));
     522           1 :       object.assigneeUpdates().forEach(u -> b.addAssigneeUpdate(toAssigneeStatusUpdateProto(u)));
     523           1 :       object
     524           1 :           .submitRecords()
     525           1 :           .forEach(r -> b.addSubmitRecord(GSON.toJson(new StoredSubmitRecord(r))));
     526           1 :       object
     527           1 :           .changeMessages()
     528           1 :           .forEach(m -> b.addChangeMessage(ChangeMessageProtoConverter.INSTANCE.toProto(m)));
     529           1 :       object.publishedComments().values().forEach(c -> b.addPublishedComment(GSON.toJson(c)));
     530           1 :       object
     531           1 :           .submitRequirementsResult()
     532           1 :           .forEach(
     533             :               sr ->
     534           1 :                   b.addSubmitRequirementResult(
     535           1 :                       SubmitRequirementProtoConverter.INSTANCE.toProto(sr)));
     536           1 :       b.setUpdateCount(object.updateCount());
     537           1 :       if (object.mergedOn() != null) {
     538           1 :         b.setMergedOnMillis(object.mergedOn().toEpochMilli());
     539           1 :         b.setHasMergedOn(true);
     540             :       }
     541             : 
     542           1 :       return Protos.toByteArray(b.build());
     543             :     }
     544             : 
     545             :     private static ChangeColumnsProto toChangeColumnsProto(ChangeColumns cols) {
     546             :       ChangeColumnsProto.Builder b =
     547           1 :           ChangeColumnsProto.newBuilder()
     548           1 :               .setChangeKey(cols.changeKey().get())
     549           1 :               .setCreatedOnMillis(cols.createdOn().toEpochMilli())
     550           1 :               .setLastUpdatedOnMillis(cols.lastUpdatedOn().toEpochMilli())
     551           1 :               .setOwner(cols.owner().get())
     552           1 :               .setBranch(cols.branch());
     553           1 :       if (cols.currentPatchSetId() != null) {
     554           1 :         b.setCurrentPatchSetId(cols.currentPatchSetId().get()).setHasCurrentPatchSetId(true);
     555             :       }
     556           1 :       b.setSubject(cols.subject());
     557           1 :       if (cols.topic() != null) {
     558           1 :         b.setTopic(cols.topic()).setHasTopic(true);
     559             :       }
     560           1 :       if (cols.originalSubject() != null) {
     561           1 :         b.setOriginalSubject(cols.originalSubject()).setHasOriginalSubject(true);
     562             :       }
     563           1 :       if (cols.submissionId() != null) {
     564           1 :         b.setSubmissionId(cols.submissionId()).setHasSubmissionId(true);
     565             :       }
     566           1 :       if (cols.status() != null) {
     567           1 :         b.setStatus(STATUS_CONVERTER.reverse().convert(cols.status())).setHasStatus(true);
     568             :       }
     569           1 :       b.setIsPrivate(cols.isPrivate())
     570           1 :           .setWorkInProgress(cols.workInProgress())
     571           1 :           .setReviewStarted(cols.reviewStarted());
     572           1 :       if (cols.revertOf() != null) {
     573           1 :         b.setRevertOf(cols.revertOf().get()).setHasRevertOf(true);
     574             :       }
     575           1 :       if (cols.cherryPickOf() != null) {
     576           0 :         b.setCherryPickOf(cols.cherryPickOf().getCommaSeparatedChangeAndPatchSetId())
     577           0 :             .setHasCherryPickOf(true);
     578             :       }
     579           1 :       return b.build();
     580             :     }
     581             : 
     582             :     private static ReviewerSetEntryProto toReviewerSetEntry(
     583             :         Table.Cell<ReviewerStateInternal, Account.Id, Instant> c) {
     584           1 :       return ReviewerSetEntryProto.newBuilder()
     585           1 :           .setState(REVIEWER_STATE_CONVERTER.reverse().convert(c.getRowKey()))
     586           1 :           .setAccountId(c.getColumnKey().get())
     587           1 :           .setTimestampMillis(c.getValue().toEpochMilli())
     588           1 :           .build();
     589             :     }
     590             : 
     591             :     private static ReviewerByEmailSetEntryProto toReviewerByEmailSetEntry(
     592             :         Table.Cell<ReviewerStateInternal, Address, Instant> c) {
     593           1 :       return ReviewerByEmailSetEntryProto.newBuilder()
     594           1 :           .setState(REVIEWER_STATE_CONVERTER.reverse().convert(c.getRowKey()))
     595           1 :           .setAddress(c.getColumnKey().toHeaderString())
     596           1 :           .setTimestampMillis(c.getValue().toEpochMilli())
     597           1 :           .build();
     598             :     }
     599             : 
     600             :     private static ReviewerStatusUpdateProto toReviewerStatusUpdateProto(ReviewerStatusUpdate u) {
     601           1 :       return ReviewerStatusUpdateProto.newBuilder()
     602           1 :           .setTimestampMillis(u.date().toEpochMilli())
     603           1 :           .setUpdatedBy(u.updatedBy().get())
     604           1 :           .setReviewer(u.reviewer().get())
     605           1 :           .setState(REVIEWER_STATE_CONVERTER.reverse().convert(u.state()))
     606           1 :           .build();
     607             :     }
     608             : 
     609             :     private static AttentionSetUpdateProto toAttentionSetUpdateProto(
     610             :         AttentionSetUpdate attentionSetUpdate) {
     611           1 :       return AttentionSetUpdateProto.newBuilder()
     612           1 :           .setTimestampMillis(attentionSetUpdate.timestamp().toEpochMilli())
     613           1 :           .setAccount(attentionSetUpdate.account().get())
     614           1 :           .setOperation(attentionSetUpdate.operation().name())
     615           1 :           .setReason(attentionSetUpdate.reason())
     616           1 :           .build();
     617             :     }
     618             : 
     619             :     private static AssigneeStatusUpdateProto toAssigneeStatusUpdateProto(AssigneeStatusUpdate u) {
     620             :       AssigneeStatusUpdateProto.Builder builder =
     621           1 :           AssigneeStatusUpdateProto.newBuilder()
     622           1 :               .setTimestampMillis(u.date().toEpochMilli())
     623           1 :               .setUpdatedBy(u.updatedBy().get())
     624           1 :               .setHasCurrentAssignee(u.currentAssignee().isPresent());
     625             : 
     626           1 :       u.currentAssignee().ifPresent(assignee -> builder.setCurrentAssignee(assignee.get()));
     627           1 :       return builder.build();
     628             :     }
     629             : 
     630             :     @Override
     631             :     public ChangeNotesState deserialize(byte[] in) {
     632           1 :       ChangeNotesStateProto proto = Protos.parseUnchecked(ChangeNotesStateProto.parser(), in);
     633           1 :       Change.Id changeId = Change.id(proto.getChangeId());
     634             : 
     635             :       ChangeNotesState.Builder b =
     636           1 :           builder()
     637           1 :               .metaId(ObjectIdConverter.create().fromByteString(proto.getMetaId()))
     638           1 :               .changeId(changeId)
     639           1 :               .columns(toChangeColumns(changeId, proto.getColumns()))
     640           1 :               .serverId(proto.getHasServerId() ? proto.getServerId() : null)
     641           1 :               .hashtags(proto.getHashtagList())
     642           1 :               .patchSets(
     643           1 :                   proto.getPatchSetList().stream()
     644           1 :                       .map(msg -> PatchSetProtoConverter.INSTANCE.fromProto(msg))
     645           1 :                       .map(ps -> Maps.immutableEntry(ps.id(), ps))
     646           1 :                       .collect(toImmutableList()))
     647           1 :               .approvals(
     648           1 :                   proto.getApprovalList().stream()
     649           1 :                       .map(msg -> PatchSetApprovalProtoConverter.INSTANCE.fromProto(msg))
     650           1 :                       .map(a -> Maps.immutableEntry(a.patchSetId(), a))
     651           1 :                       .collect(toImmutableList()))
     652           1 :               .reviewers(toReviewerSet(proto.getReviewerList()))
     653           1 :               .reviewersByEmail(toReviewerByEmailSet(proto.getReviewerByEmailList()))
     654           1 :               .pendingReviewers(toReviewerSet(proto.getPendingReviewerList()))
     655           1 :               .pendingReviewersByEmail(toReviewerByEmailSet(proto.getPendingReviewerByEmailList()))
     656           1 :               .allPastReviewers(
     657           1 :                   proto.getPastReviewerList().stream().map(Account::id).collect(toImmutableList()))
     658           1 :               .reviewerUpdates(toReviewerStatusUpdateList(proto.getReviewerUpdateList()))
     659           1 :               .attentionSet(toAttentionSetUpdates(proto.getAttentionSetUpdateList()))
     660           1 :               .allAttentionSetUpdates(
     661           1 :                   toAllAttentionSetUpdates(proto.getAllAttentionSetUpdateList()))
     662           1 :               .assigneeUpdates(toAssigneeStatusUpdateList(proto.getAssigneeUpdateList()))
     663           1 :               .submitRecords(
     664           1 :                   proto.getSubmitRecordList().stream()
     665           1 :                       .map(r -> GSON.fromJson(r, StoredSubmitRecord.class).toSubmitRecord())
     666           1 :                       .collect(toImmutableList()))
     667           1 :               .changeMessages(
     668           1 :                   proto.getChangeMessageList().stream()
     669           1 :                       .map(msg -> ChangeMessageProtoConverter.INSTANCE.fromProto(msg))
     670           1 :                       .collect(toImmutableList()))
     671           1 :               .publishedComments(
     672           1 :                   proto.getPublishedCommentList().stream()
     673           1 :                       .map(r -> GSON.fromJson(r, HumanComment.class))
     674           1 :                       .collect(toImmutableListMultimap(HumanComment::getCommitId, c -> c)))
     675           1 :               .submitRequirementsResult(
     676           1 :                   proto.getSubmitRequirementResultList().stream()
     677           1 :                       .map(sr -> SubmitRequirementProtoConverter.INSTANCE.fromProto(sr))
     678           1 :                       .collect(toImmutableList()))
     679           1 :               .updateCount(proto.getUpdateCount())
     680           1 :               .mergedOn(
     681           1 :                   proto.getHasMergedOn() ? Instant.ofEpochMilli(proto.getMergedOnMillis()) : null);
     682           1 :       return b.build();
     683             :     }
     684             : 
     685             :     private static ChangeColumns toChangeColumns(Change.Id changeId, ChangeColumnsProto proto) {
     686             :       ChangeColumns.Builder b =
     687           1 :           ChangeColumns.builder()
     688           1 :               .changeKey(Change.key(proto.getChangeKey()))
     689           1 :               .createdOn(Instant.ofEpochMilli(proto.getCreatedOnMillis()))
     690           1 :               .lastUpdatedOn(Instant.ofEpochMilli(proto.getLastUpdatedOnMillis()))
     691           1 :               .owner(Account.id(proto.getOwner()))
     692           1 :               .branch(proto.getBranch());
     693           1 :       if (proto.getHasCurrentPatchSetId()) {
     694           1 :         b.currentPatchSetId(PatchSet.id(changeId, proto.getCurrentPatchSetId()));
     695             :       }
     696           1 :       b.subject(proto.getSubject());
     697           1 :       if (proto.getHasTopic()) {
     698           1 :         b.topic(proto.getTopic());
     699             :       }
     700           1 :       if (proto.getHasOriginalSubject()) {
     701           1 :         b.originalSubject(proto.getOriginalSubject());
     702             :       }
     703           1 :       if (proto.getHasSubmissionId()) {
     704           1 :         b.submissionId(proto.getSubmissionId());
     705             :       }
     706           1 :       if (proto.getHasStatus()) {
     707           1 :         b.status(STATUS_CONVERTER.convert(proto.getStatus()));
     708             :       }
     709           1 :       b.isPrivate(proto.getIsPrivate())
     710           1 :           .workInProgress(proto.getWorkInProgress())
     711           1 :           .reviewStarted(proto.getReviewStarted());
     712           1 :       if (proto.getHasRevertOf()) {
     713           1 :         b.revertOf(Change.id(proto.getRevertOf()));
     714             :       }
     715           1 :       if (proto.getHasCherryPickOf()) {
     716           0 :         b.cherryPickOf(PatchSet.Id.parse(proto.getCherryPickOf()));
     717             :       }
     718           1 :       return b.build();
     719             :     }
     720             : 
     721             :     private static ReviewerSet toReviewerSet(List<ReviewerSetEntryProto> protos) {
     722             :       ImmutableTable.Builder<ReviewerStateInternal, Account.Id, Instant> b =
     723           1 :           ImmutableTable.builder();
     724           1 :       for (ReviewerSetEntryProto e : protos) {
     725           1 :         b.put(
     726           1 :             REVIEWER_STATE_CONVERTER.convert(e.getState()),
     727           1 :             Account.id(e.getAccountId()),
     728           1 :             Instant.ofEpochMilli(e.getTimestampMillis()));
     729           1 :       }
     730           1 :       return ReviewerSet.fromTable(b.build());
     731             :     }
     732             : 
     733             :     private static ReviewerByEmailSet toReviewerByEmailSet(
     734             :         List<ReviewerByEmailSetEntryProto> protos) {
     735           1 :       ImmutableTable.Builder<ReviewerStateInternal, Address, Instant> b = ImmutableTable.builder();
     736           1 :       for (ReviewerByEmailSetEntryProto e : protos) {
     737           1 :         b.put(
     738           1 :             REVIEWER_STATE_CONVERTER.convert(e.getState()),
     739           1 :             Address.parse(e.getAddress()),
     740           1 :             Instant.ofEpochMilli(e.getTimestampMillis()));
     741           1 :       }
     742           1 :       return ReviewerByEmailSet.fromTable(b.build());
     743             :     }
     744             : 
     745             :     private static ImmutableList<ReviewerStatusUpdate> toReviewerStatusUpdateList(
     746             :         List<ReviewerStatusUpdateProto> protos) {
     747           1 :       ImmutableList.Builder<ReviewerStatusUpdate> b = ImmutableList.builder();
     748           1 :       for (ReviewerStatusUpdateProto proto : protos) {
     749           1 :         b.add(
     750           1 :             ReviewerStatusUpdate.create(
     751           1 :                 Instant.ofEpochMilli(proto.getTimestampMillis()),
     752           1 :                 Account.id(proto.getUpdatedBy()),
     753           1 :                 Account.id(proto.getReviewer()),
     754           1 :                 REVIEWER_STATE_CONVERTER.convert(proto.getState())));
     755           1 :       }
     756           1 :       return b.build();
     757             :     }
     758             : 
     759             :     private static ImmutableSet<AttentionSetUpdate> toAttentionSetUpdates(
     760             :         List<AttentionSetUpdateProto> protos) {
     761           1 :       ImmutableSet.Builder<AttentionSetUpdate> b = ImmutableSet.builder();
     762           1 :       for (AttentionSetUpdateProto proto : protos) {
     763           1 :         b.add(
     764           1 :             AttentionSetUpdate.createFromRead(
     765           1 :                 Instant.ofEpochMilli(proto.getTimestampMillis()),
     766           1 :                 Account.id(proto.getAccount()),
     767           1 :                 AttentionSetUpdate.Operation.valueOf(proto.getOperation()),
     768           1 :                 proto.getReason()));
     769           1 :       }
     770           1 :       return b.build();
     771             :     }
     772             : 
     773             :     private static ImmutableList<AttentionSetUpdate> toAllAttentionSetUpdates(
     774             :         List<AttentionSetUpdateProto> protos) {
     775           1 :       ImmutableList.Builder<AttentionSetUpdate> b = ImmutableList.builder();
     776           1 :       for (AttentionSetUpdateProto proto : protos) {
     777           1 :         b.add(
     778           1 :             AttentionSetUpdate.createFromRead(
     779           1 :                 Instant.ofEpochMilli(proto.getTimestampMillis()),
     780           1 :                 Account.id(proto.getAccount()),
     781           1 :                 AttentionSetUpdate.Operation.valueOf(proto.getOperation()),
     782           1 :                 proto.getReason()));
     783           1 :       }
     784           1 :       return b.build();
     785             :     }
     786             : 
     787             :     private static ImmutableList<AssigneeStatusUpdate> toAssigneeStatusUpdateList(
     788             :         List<AssigneeStatusUpdateProto> protos) {
     789           1 :       ImmutableList.Builder<AssigneeStatusUpdate> b = ImmutableList.builder();
     790           1 :       for (AssigneeStatusUpdateProto proto : protos) {
     791           1 :         b.add(
     792           1 :             AssigneeStatusUpdate.create(
     793           1 :                 Instant.ofEpochMilli(proto.getTimestampMillis()),
     794           1 :                 Account.id(proto.getUpdatedBy()),
     795           1 :                 proto.getHasCurrentAssignee()
     796           1 :                     ? Optional.of(Account.id(proto.getCurrentAssignee()))
     797           1 :                     : Optional.empty()));
     798           1 :       }
     799           1 :       return b.build();
     800             :     }
     801             :   }
     802             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750