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.gerrit.server.CommentsUtil.COMMENT_ORDER; 19 : import static java.nio.charset.StandardCharsets.UTF_8; 20 : 21 : import com.google.common.collect.ListMultimap; 22 : import com.google.common.collect.Maps; 23 : import com.google.common.collect.MultimapBuilder; 24 : import com.google.gerrit.common.Nullable; 25 : import com.google.gerrit.entities.Comment; 26 : import com.google.gerrit.entities.SubmitRequirementResult; 27 : import java.io.ByteArrayOutputStream; 28 : import java.io.IOException; 29 : import java.io.OutputStream; 30 : import java.io.OutputStreamWriter; 31 : import java.util.ArrayList; 32 : import java.util.Collections; 33 : import java.util.Comparator; 34 : import java.util.HashMap; 35 : import java.util.HashSet; 36 : import java.util.List; 37 : import java.util.Map; 38 : import java.util.Set; 39 : import java.util.stream.Collectors; 40 : import org.eclipse.jgit.lib.AnyObjectId; 41 : import org.eclipse.jgit.lib.ObjectId; 42 : 43 : class RevisionNoteBuilder { 44 : /** Construct a new RevisionNoteMap, seeding it with an existing (immutable) RevisionNoteMap */ 45 : static class Cache { 46 : private final RevisionNoteMap<? extends RevisionNote<? extends Comment>> revisionNoteMap; 47 : private final Map<ObjectId, RevisionNoteBuilder> builders; 48 : 49 64 : Cache(RevisionNoteMap<? extends RevisionNote<? extends Comment>> revisionNoteMap) { 50 64 : this.revisionNoteMap = revisionNoteMap; 51 64 : this.builders = new HashMap<>(); 52 64 : } 53 : 54 : RevisionNoteBuilder get(AnyObjectId commitId) { 55 64 : RevisionNoteBuilder b = builders.get(commitId); 56 64 : if (b == null) { 57 64 : b = new RevisionNoteBuilder(revisionNoteMap.revisionNotes.get(commitId)); 58 64 : builders.put(commitId.copy(), b); 59 : } 60 64 : return b; 61 : } 62 : 63 : Map<ObjectId, RevisionNoteBuilder> getBuilders() { 64 64 : return Collections.unmodifiableMap(builders); 65 : } 66 : } 67 : 68 : /** Submit requirements are sorted w.r.t. their names before storing in NoteDb. */ 69 64 : private final Comparator<SubmitRequirementResult> SUBMIT_REQUIREMENT_RESULT_COMPARATOR = 70 64 : Comparator.comparing(sr -> sr.submitRequirement().name()); 71 : 72 : final byte[] baseRaw; 73 : private final List<? extends Comment> baseComments; 74 : final Map<Comment.Key, Comment> put; 75 : private final Set<Comment.Key> delete; 76 : 77 : /** 78 : * Submit requirement results to be stored in the revision note. If this field is null, we don't 79 : * store results in the revision note. Otherwise, we store a "submit requirements" section in the 80 : * revision note even if it's empty. 81 : */ 82 : @Nullable private List<SubmitRequirementResult> submitRequirementResults; 83 : 84 : private String pushCert; 85 : 86 64 : private RevisionNoteBuilder(RevisionNote<? extends Comment> base) { 87 64 : if (base != null) { 88 28 : baseRaw = base.getRaw(); 89 28 : baseComments = base.getEntities(); 90 28 : put = Maps.newHashMapWithExpectedSize(baseComments.size()); 91 28 : if (base instanceof ChangeRevisionNote) { 92 27 : pushCert = ((ChangeRevisionNote) base).getPushCert(); 93 27 : submitRequirementResults = ((ChangeRevisionNote) base).getSubmitRequirementsResult(); 94 : } 95 : } else { 96 64 : baseRaw = new byte[0]; 97 64 : baseComments = Collections.emptyList(); 98 64 : put = new HashMap<>(); 99 64 : pushCert = null; 100 : } 101 64 : delete = new HashSet<>(); 102 64 : } 103 : 104 : public byte[] build(ChangeNoteUtil noteUtil) throws IOException { 105 9 : return build(noteUtil.getChangeNoteJson()); 106 : } 107 : 108 : public byte[] build(ChangeNoteJson changeNoteJson) throws IOException { 109 64 : ByteArrayOutputStream out = new ByteArrayOutputStream(); 110 64 : buildNoteJson(changeNoteJson, out); 111 64 : return out.toByteArray(); 112 : } 113 : 114 : void putComment(Comment comment) { 115 29 : checkArgument(!delete.contains(comment.key), "cannot both delete and put %s", comment.key); 116 29 : put.put(comment.key, comment); 117 29 : } 118 : 119 : /** 120 : * Call this method to designate that we should store submit requirement results in the revision 121 : * note. Even if no results are added, an empty submit requirements section will be added. 122 : */ 123 : void createEmptySubmitRequirementResults() { 124 54 : submitRequirementResults = new ArrayList<>(); 125 54 : } 126 : 127 : void clearSubmitRequirementResults() { 128 2 : submitRequirementResults = null; 129 2 : } 130 : 131 : void putSubmitRequirementResult(SubmitRequirementResult result) { 132 2 : if (submitRequirementResults == null) { 133 2 : submitRequirementResults = new ArrayList<>(); 134 : } 135 2 : submitRequirementResults.add(result); 136 2 : } 137 : 138 : void deleteComment(Comment.Key key) { 139 27 : checkArgument(!put.containsKey(key), "cannot both delete and put %s", key); 140 27 : delete.add(key); 141 27 : } 142 : 143 : void setPushCertificate(String pushCert) { 144 1 : this.pushCert = pushCert; 145 1 : } 146 : 147 : private ListMultimap<Integer, Comment> buildCommentMap() { 148 64 : ListMultimap<Integer, Comment> all = MultimapBuilder.hashKeys().arrayListValues().build(); 149 : 150 64 : for (Comment c : baseComments) { 151 22 : if (!delete.contains(c.key) && !put.containsKey(c.key)) { 152 20 : all.put(c.key.patchSetId, c); 153 : } 154 22 : } 155 64 : for (Comment c : put.values()) { 156 29 : if (!delete.contains(c.key)) { 157 29 : all.put(c.key.patchSetId, c); 158 : } 159 29 : } 160 64 : return all; 161 : } 162 : 163 : private void buildNoteJson(ChangeNoteJson noteUtil, OutputStream out) throws IOException { 164 64 : ListMultimap<Integer, Comment> comments = buildCommentMap(); 165 64 : if (submitRequirementResults == null && comments.isEmpty() && pushCert == null) { 166 27 : return; 167 : } 168 : 169 64 : RevisionNoteData data = new RevisionNoteData(); 170 64 : data.comments = COMMENT_ORDER.sortedCopy(comments.values()); 171 64 : data.pushCert = pushCert; 172 64 : data.submitRequirementResults = 173 64 : submitRequirementResults == null 174 29 : ? null 175 54 : : submitRequirementResults.stream() 176 54 : .sorted(SUBMIT_REQUIREMENT_RESULT_COMPARATOR) 177 64 : .collect(Collectors.toList()); 178 : 179 64 : try (OutputStreamWriter osw = new OutputStreamWriter(out, UTF_8)) { 180 64 : noteUtil.getGson().toJson(data, osw); 181 : } 182 64 : } 183 : }