Merge changes I9f3acf5b,Ia85d8e87

* changes:
  [project.config] Replace Guava collections with native Java ones
  [project.config] Add tests for reading comment links
diff --git a/java/com/google/gerrit/server/git/ProjectConfig.java b/java/com/google/gerrit/server/git/ProjectConfig.java
index fec1ae3..09f26f1 100644
--- a/java/com/google/gerrit/server/git/ProjectConfig.java
+++ b/java/com/google/gerrit/server/git/ProjectConfig.java
@@ -23,9 +23,7 @@
 import com.google.common.base.Splitter;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
 import com.google.common.primitives.Shorts;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.ContributorAgreement;
@@ -546,7 +544,7 @@
 
   private void loadExtensionPanelSections(Config rc) {
     Map<String, String> lowerNames = Maps.newHashMapWithExpectedSize(2);
-    extensionPanelSections = Maps.newLinkedHashMap();
+    extensionPanelSections = new LinkedHashMap<>();
     for (String name : rc.getSubsections(EXTENSION_PANELS)) {
       String lower = name.toLowerCase();
       if (lowerNames.containsKey(lower)) {
@@ -914,7 +912,7 @@
 
   private void loadCommentLinkSections(Config rc) {
     Set<String> subsections = rc.getSubsections(COMMENTLINK);
-    commentLinkSections = Lists.newArrayListWithCapacity(subsections.size());
+    commentLinkSections = new ArrayList<>(subsections.size());
     for (String name : subsections) {
       try {
         commentLinkSections.add(buildCommentLink(rc, name, false));
@@ -1153,7 +1151,7 @@
       if (nc.getNotify().equals(EnumSet.of(NotifyType.ALL))) {
         rc.unset(NOTIFY, nc.getName(), KEY_TYPE);
       } else {
-        List<String> types = Lists.newArrayListWithCapacity(4);
+        List<String> types = new ArrayList<>(4);
         for (NotifyType t : NotifyType.values()) {
           if (nc.isNotify(t)) {
             types.add(StringUtils.toLowerCase(t.name()));
@@ -1258,15 +1256,15 @@
   }
 
   private void saveLabelSections(Config rc) {
-    List<String> existing = Lists.newArrayList(rc.getSubsections(LABEL));
-    if (!Lists.newArrayList(labelSections.keySet()).equals(existing)) {
+    List<String> existing = new ArrayList<>(rc.getSubsections(LABEL));
+    if (!new ArrayList<>(labelSections.keySet()).equals(existing)) {
       // Order of sections changed, remove and rewrite them all.
       for (String name : existing) {
         rc.unsetSection(LABEL, name);
       }
     }
 
-    Set<String> toUnset = Sets.newHashSet(existing);
+    Set<String> toUnset = new HashSet<>(existing);
     for (Map.Entry<String, LabelType> e : labelSections.entrySet()) {
       String name = e.getKey();
       LabelType label = e.getValue();
@@ -1325,7 +1323,7 @@
           LabelType.DEF_COPY_ALL_SCORES_ON_MERGE_FIRST_PARENT_UPDATE);
       setBooleanConfigKey(
           rc, LABEL, name, KEY_CAN_OVERRIDE, label.canOverride(), LabelType.DEF_CAN_OVERRIDE);
-      List<String> values = Lists.newArrayListWithCapacity(label.getValues().size());
+      List<String> values = new ArrayList<>(label.getValues().size());
       for (LabelValue value : label.getValues()) {
         values.add(value.format().trim());
       }
@@ -1347,7 +1345,7 @@
   }
 
   private void savePluginSections(Config rc, Set<AccountGroup.UUID> keepGroups) {
-    List<String> existing = Lists.newArrayList(rc.getSubsections(PLUGIN));
+    List<String> existing = new ArrayList<>(rc.getSubsections(PLUGIN));
     for (String name : existing) {
       rc.unsetSection(PLUGIN, name);
     }
diff --git a/javatests/com/google/gerrit/server/git/ProjectConfigTest.java b/javatests/com/google/gerrit/server/git/ProjectConfigTest.java
index 627fc27..68ec482 100644
--- a/javatests/com/google/gerrit/server/git/ProjectConfigTest.java
+++ b/javatests/com/google/gerrit/server/git/ProjectConfigTest.java
@@ -16,6 +16,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.common.truth.Truth8.assertThat;
 
 import com.google.common.collect.Iterables;
 import com.google.gerrit.common.data.AccessSection;
@@ -29,10 +30,12 @@
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.config.PluginConfig;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.project.CommentLinkInfoImpl;
 import com.google.gerrit.server.project.testing.Util;
 import com.google.gerrit.testing.GerritBaseTests;
 import java.io.IOException;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Map;
 import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -461,6 +464,109 @@
                 + "\n");
   }
 
+  @Test
+  public void readCommentLinks() throws Exception {
+    RevCommit rev =
+        tr.commit()
+            .add(
+                "project.config",
+                "[commentlink \"bugzilla\"]\n"
+                    + "\tmatch = \"(bug\\\\s+#?)(\\\\d+)\"\n"
+                    + "\tlink = http://bugs.example.com/show_bug.cgi?id=$2")
+            .create();
+    ProjectConfig cfg = read(rev);
+    Collection<CommentLinkInfoImpl> commentLinks = cfg.getCommentLinkSections();
+    assertThat(commentLinks).hasSize(1);
+    CommentLinkInfoImpl commentLink = commentLinks.iterator().next();
+    assertThat(commentLink.name).isEqualTo("bugzilla");
+    assertThat(commentLink.match).isEqualTo("(bug\\s+#?)(\\d+)");
+    assertThat(commentLink.link).isEqualTo("http://bugs.example.com/show_bug.cgi?id=$2");
+  }
+
+  @Test
+  public void readCommentLinksNoHtmlOrLinkButEnabled() throws Exception {
+    RevCommit rev =
+        tr.commit().add("project.config", "[commentlink \"bugzilla\"]\n \tenabled = true").create();
+    ProjectConfig cfg = read(rev);
+    Collection<CommentLinkInfoImpl> commentLinks = cfg.getCommentLinkSections();
+    assertThat(commentLinks).hasSize(1);
+    CommentLinkInfoImpl commentLink = commentLinks.iterator().next();
+    assertThat(commentLink.name).isEqualTo("bugzilla");
+    assertThat(commentLink.enabled).isEqualTo(true);
+    assertThat(commentLink.match).isNull();
+    assertThat(commentLink.link).isNull();
+    assertThat(commentLink.html).isNull();
+  }
+
+  @Test
+  public void readCommentLinksNoHtmlOrLinkAndDisabled() throws Exception {
+    RevCommit rev =
+        tr.commit()
+            .add("project.config", "[commentlink \"bugzilla\"]\n \tenabled = false")
+            .create();
+    ProjectConfig cfg = read(rev);
+    Collection<CommentLinkInfoImpl> commentLinks = cfg.getCommentLinkSections();
+    assertThat(commentLinks).hasSize(1);
+    CommentLinkInfoImpl commentLink = commentLinks.iterator().next();
+    assertThat(commentLink.name).isEqualTo("bugzilla");
+    assertThat(commentLink.enabled).isEqualTo(false);
+    assertThat(commentLink.match).isNull();
+    assertThat(commentLink.link).isNull();
+    assertThat(commentLink.html).isNull();
+  }
+
+  @Test
+  public void readCommentLinkInvalidPattern() throws Exception {
+    RevCommit rev =
+        tr.commit()
+            .add(
+                "project.config",
+                "[commentlink \"bugzilla\"]\n"
+                    + "\tmatch = \"(bugs{+#?)(d+)\"\n"
+                    + "\tlink = http://bugs.example.com/show_bug.cgi?id=$2")
+            .create();
+    ProjectConfig cfg = read(rev);
+    assertThat(cfg.getCommentLinkSections()).isEmpty();
+    assertThat(cfg.getValidationErrors().stream().map(ValidationError::getMessage))
+        .containsExactly(
+            "project.config: Invalid pattern \"(bugs{+#?)(d+)\" in commentlink.bugzilla.match: "
+                + "Illegal repetition near index 4\n"
+                + "(bugs{+#?)(d+)\n"
+                + "    ^");
+  }
+
+  @Test
+  public void readCommentLinkRawHtml() throws Exception {
+    RevCommit rev =
+        tr.commit()
+            .add(
+                "project.config",
+                "[commentlink \"bugzilla\"]\n"
+                    + "\tmatch = \"(bugs#?)(d+)\"\n"
+                    + "\thtml = http://bugs.example.com/show_bug.cgi?id=$2")
+            .create();
+    ProjectConfig cfg = read(rev);
+    assertThat(cfg.getCommentLinkSections()).isEmpty();
+    assertThat(cfg.getValidationErrors().stream().map(ValidationError::getMessage))
+        .containsExactly(
+            "project.config: Error in pattern \"(bugs#?)(d+)\" in commentlink.bugzilla.match: "
+                + "Raw html replacement not allowed");
+  }
+
+  @Test
+  public void readCommentLinkMatchButNoHtmlOrLink() throws Exception {
+    RevCommit rev =
+        tr.commit()
+            .add("project.config", "[commentlink \"bugzilla\"]\n" + "\tmatch = \"(bugs#?)(d+)\"\n")
+            .create();
+    ProjectConfig cfg = read(rev);
+    assertThat(cfg.getCommentLinkSections()).isEmpty();
+    assertThat(cfg.getValidationErrors().stream().map(ValidationError::getMessage))
+        .containsExactly(
+            "project.config: Error in pattern \"(bugs#?)(d+)\" in commentlink.bugzilla.match: "
+                + "commentlink.bugzilla must have either link or html");
+  }
+
   private ProjectConfig read(RevCommit rev) throws IOException, ConfigInvalidException {
     ProjectConfig cfg = new ProjectConfig(new Project.NameKey("test"));
     cfg.load(db, rev);