Add serializer to ProjectWatchKey

This commit is the first in a series that will eventually persist the
AccountCache on disk in a similar fashion as we do for other caches.

As prerequisite, we want to {de}serialize the different parts of
AccountState.

Change-Id: I3f474829219a55575259505514233fb80f03bbde
diff --git a/java/com/google/gerrit/server/account/ProjectWatches.java b/java/com/google/gerrit/server/account/ProjectWatches.java
index b153b78..9fe61a7 100644
--- a/java/com/google/gerrit/server/account/ProjectWatches.java
+++ b/java/com/google/gerrit/server/account/ProjectWatches.java
@@ -32,6 +32,9 @@
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.Project;
+import com.google.gerrit.proto.Protos;
+import com.google.gerrit.server.cache.proto.Cache.ProjectWatchKeyProto;
+import com.google.gerrit.server.cache.serialize.CacheSerializer;
 import com.google.gerrit.server.git.ValidationError;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -79,6 +82,7 @@
 public class ProjectWatches {
   @AutoValue
   public abstract static class ProjectWatchKey {
+
     public static ProjectWatchKey create(Project.NameKey project, @Nullable String filter) {
       return new AutoValue_ProjectWatches_ProjectWatchKey(project, Strings.emptyToNull(filter));
     }
@@ -86,6 +90,26 @@
     public abstract Project.NameKey project();
 
     public abstract @Nullable String filter();
+
+    enum Serializer implements CacheSerializer<ProjectWatchKey> {
+      INSTANCE;
+
+      @Override
+      public byte[] serialize(ProjectWatchKey key) {
+        ProjectWatchKeyProto.Builder proto =
+            ProjectWatchKeyProto.newBuilder().setProject(key.project().get());
+        if (key.filter() != null) {
+          proto.setFilter(key.filter());
+        }
+        return Protos.toByteArray(proto.build());
+      }
+
+      @Override
+      public ProjectWatchKey deserialize(byte[] in) {
+        ProjectWatchKeyProto proto = Protos.parseUnchecked(ProjectWatchKeyProto.parser(), in);
+        return ProjectWatchKey.create(Project.nameKey(proto.getProject()), proto.getFilter());
+      }
+    }
   }
 
   public enum NotifyType {
diff --git a/javatests/com/google/gerrit/server/account/ProjectWatchCacheTest.java b/javatests/com/google/gerrit/server/account/ProjectWatchCacheTest.java
new file mode 100644
index 0000000..a623b82
--- /dev/null
+++ b/javatests/com/google/gerrit/server/account/ProjectWatchCacheTest.java
@@ -0,0 +1,50 @@
+// Copyright (C) 2020 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.account;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.truth.extensions.proto.ProtoTruth;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.cache.proto.Cache.ProjectWatchKeyProto;
+import org.junit.Test;
+
+/**
+ * Test to ensure that we are serializing and deserializing {@link ProjectWatches.ProjectWatchKey}
+ * correctly. This is part of the {@code AccountCache}.
+ */
+public class ProjectWatchCacheTest {
+  @Test
+  public void keyRoundTrip() throws Exception {
+    ProjectWatches.ProjectWatchKey key =
+        ProjectWatches.ProjectWatchKey.create(Project.nameKey("pro/ject"), "*");
+    byte[] serialized = ProjectWatches.ProjectWatchKey.Serializer.INSTANCE.serialize(key);
+    ProtoTruth.assertThat(ProjectWatchKeyProto.parseFrom(serialized))
+        .isEqualTo(ProjectWatchKeyProto.newBuilder().setProject("pro/ject").setFilter("*").build());
+    assertThat(ProjectWatches.ProjectWatchKey.Serializer.INSTANCE.deserialize(serialized))
+        .isEqualTo(key);
+  }
+
+  @Test
+  public void keyRoundTripNullFilter() throws Exception {
+    ProjectWatches.ProjectWatchKey key =
+        ProjectWatches.ProjectWatchKey.create(Project.nameKey("pro/ject"), null);
+    byte[] serialized = ProjectWatches.ProjectWatchKey.Serializer.INSTANCE.serialize(key);
+    ProtoTruth.assertThat(ProjectWatchKeyProto.parseFrom(serialized))
+        .isEqualTo(ProjectWatchKeyProto.newBuilder().setProject("pro/ject").build());
+    assertThat(ProjectWatches.ProjectWatchKey.Serializer.INSTANCE.deserialize(serialized))
+        .isEqualTo(key);
+  }
+}
diff --git a/proto/cache.proto b/proto/cache.proto
index b881e3d..58bd162 100644
--- a/proto/cache.proto
+++ b/proto/cache.proto
@@ -272,3 +272,10 @@
   bytes claimed_original = 2;
   bytes claimed_revert = 3;
 }
+
+// Key for com.google.gerrit.server.account.ProjectWatches.ProjectWatcheKey.
+// Next ID: 3
+message ProjectWatchKeyProto {
+  string project = 1;
+  string filter = 2;
+}