Modify quoted comment truncation rules

When a diff comment replies to another, the resulting email message
includes a quotation of the parent message with some truncation.

Formerly, messages were truncated to 75 characters or the first line of
the comment (whichever was shorter). The resulting truncated quotation
presented no indication that it had been truncated.

With this change, the message is truncated to 100 characters or the
first line of the comment, or the after the last period within 100
characters (whichever is shorter). If the message is truncated as a
result of these rules, an ellipsis is appended.

Bug: Issue 6111
Change-Id: I0e9ffb8ebd7c734860021d7a6948b181daccb034
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/CommentSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/CommentSender.java
index ce68cca..7d83c5c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/CommentSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/CommentSender.java
@@ -435,21 +435,43 @@
   }
 
   /**
-   * @return a shortened version of the given comment's message. Will be shortened to 75 characters
-   *     or the first line, whichever is shorter.
+   * @return a shortened version of the given comment's message. Will be shortened to 100 characters
+   *     or the first line, or following the last period within the first 100 characters, whichever
+   *     is shorter. If the message is shortened, an ellipsis is appended.
    */
-  private String getShortenedCommentMessage(Comment comment) {
-    String msg = comment.message.trim();
-    if (msg.length() > 75) {
-      msg = msg.substring(0, 75);
+  protected static String getShortenedCommentMessage(String message) {
+    int threshold = 100;
+    String fullMessage = message.trim();
+    String msg = fullMessage;
+
+    if (msg.length() > threshold) {
+      msg = msg.substring(0, threshold);
     }
+
     int lf = msg.indexOf('\n');
+    int period = msg.lastIndexOf('.');
+
     if (lf > 0) {
+      // Truncate if a line feed appears within the threshold.
       msg = msg.substring(0, lf);
+
+    } else if (period > 0) {
+      // Otherwise truncate if there is a period within the threshold.
+      msg = msg.substring(0, period + 1);
     }
+
+    // Append an ellipsis if the message has been truncated.
+    if (!msg.equals(fullMessage)) {
+      msg += " […]";
+    }
+
     return msg;
   }
 
+  protected static String getShortenedCommentMessage(Comment comment) {
+    return getShortenedCommentMessage(comment.message);
+  }
+
   /**
    * @return grouped inline comment data mapped to data structures that are suitable for passing
    *     into Soy.
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/send/CommentSenderTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/send/CommentSenderTest.java
new file mode 100644
index 0000000..6b6632c
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/send/CommentSenderTest.java
@@ -0,0 +1,62 @@
+// 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.mail.send;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gwtorm.server.OrmException;
+import java.util.Collections;
+import org.junit.Test;
+
+public class CommentSenderTest {
+  private static class TestSender extends CommentSender {
+    TestSender() throws OrmException {
+      super(null, null, null, null, null);
+    }
+  }
+
+  // A 100-character long string.
+  private static String chars100 = String.join("", Collections.nCopies(25, "abcd"));
+
+  @Test
+  public void shortMessageNotShortened() {
+    String message = "foo bar baz";
+    assertThat(TestSender.getShortenedCommentMessage(message)).isEqualTo(message);
+
+    message = "foo bar baz.";
+    assertThat(TestSender.getShortenedCommentMessage(message)).isEqualTo(message);
+  }
+
+  @Test
+  public void longMessageIsShortened() {
+    String message = chars100 + "x";
+    String expected = chars100 + " […]";
+    assertThat(TestSender.getShortenedCommentMessage(message)).isEqualTo(expected);
+  }
+
+  @Test
+  public void shortenedToFirstLine() {
+    String message = "abc\n" + chars100;
+    String expected = "abc […]";
+    assertThat(TestSender.getShortenedCommentMessage(message)).isEqualTo(expected);
+  }
+
+  @Test
+  public void shortenedToFirstSentence() {
+    String message = "foo bar baz. " + chars100;
+    String expected = "foo bar baz. […]";
+    assertThat(TestSender.getShortenedCommentMessage(message)).isEqualTo(expected);
+  }
+}