Extract RevisionNote.Comment and use it instead of PatchLineComment

There are 3 classes that represent an inline comment:
- CommentInfo represents an inline comment in the REST API
- PatchLineComment represents an inline comment in ReviewDb
- RevisionNote.Comment represents an inline comment in NoteDb

To support robot comments CommentInfo and RevisionNote.Comment must be
extended so that the additional fields that are specific for robot
comments can be supported. PatchLineComment should not be touched
since robot comments will only be supported with NoteDb, but not with
ReviewDb.

At the moment PatchLineComment is also used to represent inline
comments in all middle layers and in all utility classes that deal
with inline comments. This means if NoteDb is used, inline comments
come in over REST as CommentInfo, then they are converted into
PatchLineComment and for storing them in NoteDb they are converted to
RevisionNote.Comment. The intermediate transformation to
PatchLineComment is bad for implementing robot comments, since this
class should stay unchanged.

To fix this RevisionNote.Comment is extracted into an own Comment
class and then Comment is used instead of PatchLineComment in the
middle layer. This means when NoteDb is used, inline comments are only
converted once, from CommentInfo to Comment. Both types will have
extensions for robot comments (by subtypes).

For storing inline comments in ReviewDb inline comments are converted
from CommentInfo to Comment to PatchLineComment. This is better than
having the double-conversion for NoteDb, because ReviewDb will be
removed in favour of NoteDb. This means when ReviewDb is removed
PatchLineComment can then easily be deleted, as it's no longer used
all over the codebase, but only in the ReviewDb layer.

Change-Id: I53481e8231e04aeca5b924e409e97b0f1d53f516
Signed-off-by: Edwin Kempin <ekempin@google.com>
This commit is contained in:
Edwin Kempin
2016-09-19 16:30:24 +02:00
parent e24f8b44f9
commit 10ba6179cb
48 changed files with 1220 additions and 1055 deletions

View File

@@ -0,0 +1,263 @@
// 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.reviewdb.client;
import java.sql.Timestamp;
import java.util.Objects;
/**
* This class represents inline comments in NoteDb. This means it determines the
* JSON format for inline comments in the revision notes that NoteDb uses to
* persist inline comments.
* <p>
* Changing fields in this class changes the storage format of inline comments
* in NoteDb and may require a corresponding data migration (adding new optional
* fields is generally okay).
* <p>
* {@link PatchLineComment} also represents inline comments, but in ReviewDb.
* There are a few notable differences:
* <ul>
* <li>PatchLineComment knows the comment status (published or draft). For
* comments in NoteDb the status is determined by the branch in which they are
* stored (published comments are stored in the change meta ref; draft comments
* are store in refs/draft-comments branches in All-Users). Hence Comment
* doesn't need to contain the status, but the status is implicitly known by
* where the comments are read from.
* <li>PatchLineComment knows the change ID. For comments in NoteDb, the change
* ID is determined by the branch in which they are stored (the ref name
* contains the change ID). Hence Comment doesn't need to contain the change ID,
* but the change ID is implicitly known by where the comments are read from.
* </ul>
* <p>
* For all utility classes and middle layer functionality using Comment over
* PatchLineComment is preferred, as PatchLineComment will go away together with
* ReviewDb. This means Comment should be used everywhere and only for storing
* inline comment in ReviewDb a conversion to PatchLineComment is done.
* Converting Comments to PatchLineComments and vice verse is done by
* CommentsUtil#toPatchLineComments(Change.Id, PatchLineComment.Status, Iterable)
* and CommentsUtil#toComments(String, Iterable).
*/
public class Comment {
public static class Key {
public String uuid;
public String filename;
public int patchSetId;
public Key(Key k) {
this(k.uuid, k.filename, k.patchSetId);
}
public Key(String uuid, String filename, int patchSetId) {
this.uuid = uuid;
this.filename = filename;
this.patchSetId = patchSetId;
}
@Override
public String toString() {
return new StringBuilder()
.append("Comment.Key{")
.append("uuid=").append(uuid).append(',')
.append("filename=").append(filename).append(',')
.append("patchSetId=").append(patchSetId)
.append('}')
.toString();
}
@Override
public boolean equals(Object o) {
if (o instanceof Key) {
Key k = (Key) o;
return Objects.equals(uuid, k.uuid)
&& Objects.equals(filename, k.filename)
&& Objects.equals(patchSetId, k.patchSetId);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(uuid, filename, patchSetId);
}
}
public static class Identity {
int id;
public Identity(Account.Id id) {
this.id = id.get();
}
public Account.Id getId() {
return new Account.Id(id);
}
@Override
public boolean equals(Object o) {
if (o instanceof Identity) {
return Objects.equals(id, ((Identity) o).id);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public String toString() {
return new StringBuilder()
.append("Comment.Identity{")
.append("id=").append(id)
.append('}')
.toString();
}
}
public static class Range {
public int startLine;
public int startChar;
public int endLine;
public int endChar;
public Range(Range r) {
this(r.startLine, r.startChar, r.endLine, r.endChar);
}
public Range(com.google.gerrit.extensions.client.Comment.Range r) {
this(r.startLine, r.startCharacter, r.endLine, r.endCharacter);
}
public Range(int startLine, int startChar, int endLine, int endChar) {
this.startLine = startLine;
this.startChar = startChar;
this.endLine = endLine;
this.endChar = endChar;
}
@Override
public boolean equals(Object o) {
if (o instanceof Range) {
Range r = (Range) o;
return Objects.equals(startLine, r.startLine)
&& Objects.equals(startChar, r.startChar)
&& Objects.equals(endLine, r.endLine)
&& Objects.equals(endChar, r.endChar);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(startLine, startChar, endLine, endChar);
}
@Override
public String toString() {
return new StringBuilder()
.append("Comment.Range{")
.append("startLine=").append(startLine).append(',')
.append("startChar=").append(startChar).append(',')
.append("endLine=").append(endLine).append(',')
.append("endChar=").append(endChar)
.append('}')
.toString();
}
}
public Key key;
public int lineNbr;
public Identity author;
public Timestamp writtenOn;
public short side;
public String message;
public String parentUuid;
public Range range;
public String tag;
public String revId;
public String serverId;
public Comment(Comment c) {
this(new Key(c.key), c.author.getId(), new Timestamp(c.writtenOn.getTime()),
c.side, c.message, c.serverId);
this.lineNbr = c.lineNbr;
this.range = c.range != null ? new Range(c.range) : null;
this.tag = c.tag;
this.revId = c.revId;
}
public Comment(Key key, Account.Id author, Timestamp writtenOn,
short side, String message, String serverId) {
this.key = key;
this.author = new Comment.Identity(author);
this.writtenOn = writtenOn;
this.side = side;
this.message = message;
this.serverId = serverId;
}
public void setLineNbrAndRange(Integer lineNbr,
com.google.gerrit.extensions.client.Comment.Range range) {
this.lineNbr = lineNbr != null
? lineNbr
: range != null
? range.endLine
: 0;
if (range != null) {
this.range = new Comment.Range(range);
}
}
public void setRange(CommentRange range) {
this.range = range != null ? range.asCommentRange() : null;
}
public void setRevId(RevId revId) {
this.revId = revId != null ? revId.get() : null;
}
@Override
public boolean equals(Object o) {
if (o instanceof Comment) {
return Objects.equals(key, ((Comment) o).key);
}
return false;
}
@Override
public int hashCode() {
return key.hashCode();
}
@Override
public String toString() {
return new StringBuilder()
.append("Comment{")
.append("key=").append(key).append(',')
.append("lineNbr=").append(lineNbr).append(',')
.append("author=").append(author.getId().get()).append(',')
.append("writtenOn=").append(writtenOn.toString()).append(',')
.append("side=").append(side).append(',')
.append("message=").append(Objects.toString(message, "")).append(',')
.append("parentUuid=")
.append(Objects.toString(parentUuid, "")).append(',')
.append("range=").append(Objects.toString(range, "")).append(',')
.append("revId=").append(revId != null ? revId : "")
.append("tag=").append(Objects.toString(tag, ""))
.append('}')
.toString();
}
}

View File

@@ -72,6 +72,10 @@ public class CommentRange {
endCharacter = ec;
}
public Comment.Range asCommentRange() {
return new Comment.Range(startLine, startCharacter, endLine, endCharacter);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof CommentRange) {

View File

@@ -21,11 +21,24 @@ import com.google.gwtorm.client.StringKey;
import java.sql.Timestamp;
import java.util.Objects;
/** A comment left by a user on a specific line of a {@link Patch}. */
/**
* A comment left by a user on a specific line of a {@link Patch}.
*
* This class represents an inline comment in ReviewDb. It should only be used
* for writing/reading inline comments to/from ReviewDb. For all other purposes
* inline comments should be represented by {@link Comment}.
*
* @see Comment
*/
public final class PatchLineComment {
public static class Key extends StringKey<Patch.Key> {
private static final long serialVersionUID = 1L;
public static Key from(Change.Id changeId, Comment.Key key) {
return new Key(new Patch.Key(new PatchSet.Id(changeId, key.patchSetId),
key.filename), key.uuid);
}
@Column(id = 1, name = Column.NONE)
protected Patch.Key patchKey;
@@ -55,6 +68,12 @@ public final class PatchLineComment {
public void set(String newValue) {
uuid = newValue;
}
public Comment.Key asCommentKey() {
return new Comment.Key(get(),
getParentKey().getFileName(),
getParentKey().getParentKey().get());
}
}
public static final char STATUS_DRAFT = 'd';
@@ -85,6 +104,28 @@ public final class PatchLineComment {
}
}
public static PatchLineComment from(Change.Id changeId,
PatchLineComment.Status status, Comment c) {
PatchLineComment.Key key = new PatchLineComment.Key(
new Patch.Key(new PatchSet.Id(changeId, c.key.patchSetId),
c.key.filename),
c.key.uuid);
PatchLineComment plc = new PatchLineComment(key, c.lineNbr,
c.author.getId(), c.parentUuid, c.writtenOn);
plc.setSide(c.side);
plc.setMessage(c.message);
if (c.range != null) {
Comment.Range r = c.range;
plc.setRange(
new CommentRange(r.startLine, r.startChar, r.endLine, r.endChar));
}
plc.setTag(c.tag);
plc.setRevId(new RevId(c.revId));
plc.setStatus(status);
return plc;
}
@Column(id = 1, name = Column.NONE)
protected Key key;
@@ -260,6 +301,17 @@ public final class PatchLineComment {
return tag;
}
public Comment asComment(String serverId) {
Comment c = new Comment(key.asCommentKey(), author, writtenOn, side,
message, serverId);
c.setRevId(revId);
c.setRange(range);
c.lineNbr = lineNbr;
c.parentUuid = parentUuid;
c.tag = tag;
return c;
}
@Override
public boolean equals(Object o) {
if (o instanceof PatchLineComment) {