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.entities;
16 :
17 : import com.google.common.base.MoreObjects;
18 : import com.google.common.base.MoreObjects.ToStringHelper;
19 : import com.google.gerrit.common.Nullable;
20 : import java.sql.Timestamp;
21 : import java.time.Instant;
22 : import java.util.Comparator;
23 : import java.util.Objects;
24 : import org.eclipse.jgit.lib.AnyObjectId;
25 : import org.eclipse.jgit.lib.ObjectId;
26 :
27 : /**
28 : * This class is a base class that can be extended by the different types of inline comment
29 : * entities.
30 : *
31 : * <p>Changing fields in this class changes the storage format of inline comments in NoteDb and may
32 : * require a corresponding data migration (adding new optional fields is generally okay).
33 : *
34 : * <p>Consider updating {@link #getCommentFieldApproximateSize()} when adding/changing fields.
35 : */
36 : public abstract class Comment {
37 103 : public enum Status {
38 103 : DRAFT('d'),
39 :
40 103 : PUBLISHED('P');
41 :
42 : private final char code;
43 :
44 103 : Status(char c) {
45 103 : code = c;
46 103 : }
47 :
48 : public char getCode() {
49 0 : return code;
50 : }
51 :
52 : @Nullable
53 : public static Status forCode(char c) {
54 0 : for (Status s : Status.values()) {
55 0 : if (s.code == c) {
56 0 : return s;
57 : }
58 : }
59 0 : return null;
60 : }
61 : }
62 :
63 : public static final class Key {
64 : public String uuid;
65 : public String filename;
66 : public int patchSetId;
67 :
68 : public Key(Key k) {
69 12 : this(k.uuid, k.filename, k.patchSetId);
70 12 : }
71 :
72 30 : public Key(String uuid, String filename, int patchSetId) {
73 30 : this.uuid = uuid;
74 30 : this.filename = filename;
75 30 : this.patchSetId = patchSetId;
76 30 : }
77 :
78 : @Override
79 : public String toString() {
80 0 : return MoreObjects.toStringHelper(this)
81 0 : .add("uuid", uuid)
82 0 : .add("filename", filename)
83 0 : .add("patchSetId", patchSetId)
84 0 : .toString();
85 : }
86 :
87 : @Override
88 : public boolean equals(Object o) {
89 24 : if (o instanceof Key) {
90 24 : Key k = (Key) o;
91 24 : return Objects.equals(uuid, k.uuid)
92 24 : && Objects.equals(filename, k.filename)
93 : && patchSetId == k.patchSetId;
94 : }
95 0 : return false;
96 : }
97 :
98 : @Override
99 : public int hashCode() {
100 29 : return Objects.hash(uuid, filename, patchSetId);
101 : }
102 : }
103 :
104 : public static final class Identity {
105 : int id;
106 :
107 30 : public Identity(Account.Id id) {
108 30 : this.id = id.get();
109 30 : }
110 :
111 : public Account.Id getId() {
112 29 : return Account.id(id);
113 : }
114 :
115 : @Override
116 : public boolean equals(Object o) {
117 2 : if (o instanceof Identity) {
118 2 : return id == ((Identity) o).id;
119 : }
120 0 : return false;
121 : }
122 :
123 : @Override
124 : public int hashCode() {
125 26 : return Objects.hash(id);
126 : }
127 :
128 : @Override
129 : public String toString() {
130 0 : return MoreObjects.toStringHelper(this).add("id", id).toString();
131 : }
132 : }
133 :
134 : /**
135 : * The Range class defines continuous range of character.
136 : *
137 : * <p>The pair (startLine, startChar) defines the first character in the range. The pair (endLine,
138 : * endChar) defines the first character AFTER the range (i.e. it doesn't belong the range).
139 : * (endLine, endChar) must be a valid character inside text, except EOF case.
140 : *
141 : * <p>Special cases:
142 : *
143 : * <ul>
144 : * <li>Zero length range: (startLine, startChar) = (endLine, endChar). Range defines insert
145 : * position right before the (startLine, startChar) character (for {@link FixReplacement})
146 : * <li>EOF case - range includes the last character in the file:
147 : * <ul>
148 : * <li>if a file ends with EOL mark, then (endLine, endChar) = (num_of_lines + 1, 0)
149 : * <li>if a file doesn't end with EOL mark, then (endLine, endChar) = (num_of_lines,
150 : * num_of_chars_in_last_line)
151 : * </ul>
152 : * </ul>
153 : */
154 : public static final class Range implements Comparable<Range> {
155 14 : private static final Comparator<Range> RANGE_COMPARATOR =
156 14 : Comparator.<Range>comparingInt(range -> range.startLine)
157 14 : .thenComparingInt(range -> range.startChar)
158 14 : .thenComparingInt(range -> range.endLine)
159 14 : .thenComparingInt(range -> range.endChar);
160 :
161 : public int startLine; // 1-based
162 : public int startChar; // 0-based
163 : public int endLine; // 1-based
164 : public int endChar; // 0-based
165 :
166 : public Range(Range r) {
167 3 : this(r.startLine, r.startChar, r.endLine, r.endChar);
168 3 : }
169 :
170 : public Range(com.google.gerrit.extensions.client.Comment.Range r) {
171 12 : this(r.startLine, r.startCharacter, r.endLine, r.endCharacter);
172 12 : }
173 :
174 14 : public Range(int startLine, int startChar, int endLine, int endChar) {
175 14 : this.startLine = startLine;
176 14 : this.startChar = startChar;
177 14 : this.endLine = endLine;
178 14 : this.endChar = endChar;
179 14 : }
180 :
181 : @Override
182 : public boolean equals(Object o) {
183 2 : if (o instanceof Range) {
184 2 : Range r = (Range) o;
185 2 : return startLine == r.startLine
186 : && startChar == r.startChar
187 : && endLine == r.endLine
188 : && endChar == r.endChar;
189 : }
190 1 : return false;
191 : }
192 :
193 : @Override
194 : public int hashCode() {
195 10 : return Objects.hash(startLine, startChar, endLine, endChar);
196 : }
197 :
198 : @Override
199 : public String toString() {
200 0 : return MoreObjects.toStringHelper(this)
201 0 : .add("startLine", startLine)
202 0 : .add("startChar", startChar)
203 0 : .add("endLine", endLine)
204 0 : .add("endChar", endChar)
205 0 : .toString();
206 : }
207 :
208 : @Override
209 : public int compareTo(Range otherRange) {
210 3 : return RANGE_COMPARATOR.compare(this, otherRange);
211 : }
212 : }
213 :
214 : public Key key;
215 : /** The line number (1-based) to which the comment refers, or 0 for a file comment. */
216 : public int lineNbr;
217 :
218 : public Identity author;
219 : protected Identity realAuthor;
220 :
221 : // TODO(issue-15525): Migrate this field from Timestamp to Instant
222 : public Timestamp writtenOn;
223 :
224 : public short side;
225 : public String message;
226 : public String parentUuid;
227 : public Range range;
228 : public String tag;
229 :
230 : /**
231 : * Hex commit SHA1 of the commit of the patchset to which this comment applies. Other classes call
232 : * this "commitId", but this class uses the old ReviewDb term "revId", and this field name is
233 : * serialized into JSON in NoteDb, so it can't easily be changed. Callers do not access this field
234 : * directly, and instead use the public getter/setter that wraps an ObjectId.
235 : */
236 : private String revId;
237 :
238 : public String serverId;
239 :
240 : public Comment(Comment c) {
241 4 : this(new Key(c.key), c.author.getId(), c.writtenOn.toInstant(), c.side, c.message, c.serverId);
242 4 : this.lineNbr = c.lineNbr;
243 4 : this.realAuthor = c.realAuthor;
244 4 : this.parentUuid = c.parentUuid;
245 4 : this.range = c.range != null ? new Range(c.range) : null;
246 4 : this.tag = c.tag;
247 4 : this.revId = c.revId;
248 4 : }
249 :
250 : public Comment(
251 30 : Key key, Account.Id author, Instant writtenOn, short side, String message, String serverId) {
252 30 : this.key = key;
253 30 : this.author = new Comment.Identity(author);
254 30 : this.realAuthor = this.author;
255 30 : this.writtenOn = Timestamp.from(writtenOn);
256 30 : this.side = side;
257 30 : this.message = message;
258 30 : this.serverId = serverId;
259 30 : }
260 :
261 : public void setWrittenOn(Instant writtenOn) {
262 3 : this.writtenOn = Timestamp.from(writtenOn);
263 3 : }
264 :
265 : public void setLineNbrAndRange(
266 : Integer lineNbr, com.google.gerrit.extensions.client.Comment.Range range) {
267 28 : this.lineNbr = range != null ? range.endLine : lineNbr != null ? lineNbr : 0;
268 28 : if (range != null) {
269 9 : this.range = new Comment.Range(range);
270 : }
271 28 : }
272 :
273 : public void setRange(CommentRange range) {
274 1 : this.range = range != null ? range.asCommentRange() : null;
275 1 : }
276 :
277 : @Nullable
278 : public ObjectId getCommitId() {
279 29 : return revId != null ? ObjectId.fromString(revId) : null;
280 : }
281 :
282 : public void setCommitId(@Nullable AnyObjectId commitId) {
283 29 : this.revId = commitId != null ? commitId.name() : null;
284 29 : }
285 :
286 : public void setRealAuthor(Account.Id id) {
287 29 : realAuthor = id != null && id.get() != author.id ? new Comment.Identity(id) : null;
288 29 : }
289 :
290 : public Identity getRealAuthor() {
291 29 : return realAuthor != null ? realAuthor : author;
292 : }
293 :
294 : /**
295 : * Returns the comment's approximate size. This is used to enforce size limits and should
296 : * therefore include all unbounded fields (e.g. String-s).
297 : */
298 : protected int getCommentFieldApproximateSize() {
299 23 : return nullableLength(message, parentUuid, tag, revId, serverId)
300 23 : + (key != null ? nullableLength(key.filename, key.uuid) : 0);
301 : }
302 :
303 : public abstract int getApproximateSize();
304 :
305 : static int nullableLength(String... strings) {
306 23 : int length = 0;
307 23 : for (String s : strings) {
308 23 : length += s == null ? 0 : s.length();
309 : }
310 23 : return length;
311 : }
312 :
313 : @Override
314 : public boolean equals(Object o) {
315 14 : if (!(o instanceof Comment)) {
316 0 : return false;
317 : }
318 14 : Comment c = (Comment) o;
319 14 : return Objects.equals(key, c.key)
320 : && lineNbr == c.lineNbr
321 2 : && Objects.equals(author, c.author)
322 2 : && Objects.equals(realAuthor, c.realAuthor)
323 2 : && Objects.equals(writtenOn, c.writtenOn)
324 : && side == c.side
325 2 : && Objects.equals(message, c.message)
326 2 : && Objects.equals(parentUuid, c.parentUuid)
327 2 : && Objects.equals(range, c.range)
328 2 : && Objects.equals(tag, c.tag)
329 2 : && Objects.equals(revId, c.revId)
330 14 : && Objects.equals(serverId, c.serverId);
331 : }
332 :
333 : @Override
334 : public int hashCode() {
335 26 : return Objects.hash(
336 : key,
337 26 : lineNbr,
338 : author,
339 : realAuthor,
340 : writtenOn,
341 26 : side,
342 : message,
343 : parentUuid,
344 : range,
345 : tag,
346 : revId,
347 : serverId);
348 : }
349 :
350 : @Override
351 : public String toString() {
352 0 : return toStringHelper().toString();
353 : }
354 :
355 : protected ToStringHelper toStringHelper() {
356 0 : return MoreObjects.toStringHelper(this)
357 0 : .add("key", key)
358 0 : .add("lineNbr", lineNbr)
359 0 : .add("author", author.getId())
360 0 : .add("realAuthor", realAuthor != null ? realAuthor.getId() : "")
361 0 : .add("writtenOn", writtenOn)
362 0 : .add("side", side)
363 0 : .add("message", Objects.toString(message, ""))
364 0 : .add("parentUuid", Objects.toString(parentUuid, ""))
365 0 : .add("range", Objects.toString(range, ""))
366 0 : .add("revId", Objects.toString(revId, ""))
367 0 : .add("tag", Objects.toString(tag, ""));
368 : }
369 : }
|