Add protobuf serializers for the GitFileDiff and GitFileDiffCacheKey

Change-Id: Id1537c2f756732743e2ba82e8aab11c3e52f8f15
diff --git a/java/com/google/gerrit/server/patch/entities/Edit.java b/java/com/google/gerrit/server/patch/entities/Edit.java
index 683bbec..7d39d69 100644
--- a/java/com/google/gerrit/server/patch/entities/Edit.java
+++ b/java/com/google/gerrit/server/patch/entities/Edit.java
@@ -23,7 +23,7 @@
  */
 @AutoValue
 public abstract class Edit {
-  static Edit create(int beginA, int endA, int beginB, int endB) {
+  public static Edit create(int beginA, int endA, int beginB, int endB) {
     return new AutoValue_Edit(beginA, endA, beginB, endB);
   }
 
diff --git a/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiff.java b/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiff.java
index de07ed1..f91d917 100644
--- a/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiff.java
+++ b/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiff.java
@@ -22,8 +22,12 @@
 import com.google.gerrit.entities.Patch;
 import com.google.gerrit.entities.Patch.ChangeType;
 import com.google.gerrit.entities.Patch.PatchType;
+import com.google.gerrit.proto.Protos;
+import com.google.gerrit.server.cache.proto.Cache.GitFileDiffProto;
 import com.google.gerrit.server.cache.serialize.CacheSerializer;
+import com.google.gerrit.server.cache.serialize.ObjectIdConverter;
 import com.google.gerrit.server.patch.entities.Edit;
+import com.google.protobuf.Descriptors.FieldDescriptor;
 import java.io.IOException;
 import java.util.Map;
 import java.util.Optional;
@@ -75,8 +79,8 @@
         .fileHeader(FileHeaderUtil.toString(fileHeader))
         .oldPath(FileHeaderUtil.getOldPath(fileHeader))
         .newPath(FileHeaderUtil.getNewPath(fileHeader))
-        .changeType(FileHeaderUtil.getChangeType(fileHeader))
-        .patchType(FileHeaderUtil.getPatchType(fileHeader))
+        .changeType(Optional.of(FileHeaderUtil.getChangeType(fileHeader)))
+        .patchType(Optional.of(FileHeaderUtil.getPatchType(fileHeader)))
         .oldMode(Optional.of(mapFileMode(diffEntry.getOldMode())))
         .newMode(Optional.of(mapFileMode(diffEntry.getNewMode())))
         .build();
@@ -196,26 +200,106 @@
 
     public abstract Builder newMode(Optional<Patch.FileMode> value);
 
-    public abstract Builder changeType(ChangeType value);
+    public abstract Builder changeType(Optional<ChangeType> value);
 
-    public abstract Builder patchType(PatchType value);
+    public abstract Builder patchType(Optional<PatchType> value);
 
     public abstract GitFileDiff build();
   }
 
-  enum Serializer implements CacheSerializer<GitFileDiff> {
+  public enum Serializer implements CacheSerializer<GitFileDiff> {
     INSTANCE;
 
+    private static final FieldDescriptor OLD_PATH_DESCRIPTOR =
+        GitFileDiffProto.getDescriptor().findFieldByName("old_path");
+
+    private static final FieldDescriptor NEW_PATH_DESCRIPTOR =
+        GitFileDiffProto.getDescriptor().findFieldByName("new_path");
+
+    private static final FieldDescriptor OLD_MODE_DESCRIPTOR =
+        GitFileDiffProto.getDescriptor().findFieldByName("old_mode");
+
+    private static final FieldDescriptor NEW_MODE_DESCRIPTOR =
+        GitFileDiffProto.getDescriptor().findFieldByName("new_mode");
+
+    private static final FieldDescriptor CHANGE_TYPE_DESCRIPTOR =
+        GitFileDiffProto.getDescriptor().findFieldByName("change_type");
+
+    private static final FieldDescriptor PATCH_TYPE_DESCRIPTOR =
+        GitFileDiffProto.getDescriptor().findFieldByName("patch_type");
+
     @Override
-    public byte[] serialize(GitFileDiff object) {
-      // TODO(ghareeb)
-      return new byte[0];
+    public byte[] serialize(GitFileDiff gitFileDiff) {
+      ObjectIdConverter idConverter = ObjectIdConverter.create();
+      GitFileDiffProto.Builder builder =
+          GitFileDiffProto.newBuilder()
+              .setFileHeader(gitFileDiff.fileHeader())
+              .setOldId(idConverter.toByteString(gitFileDiff.oldId().toObjectId()))
+              .setNewId(idConverter.toByteString(gitFileDiff.newId().toObjectId()));
+      gitFileDiff
+          .edits()
+          .forEach(
+              e ->
+                  builder.addEdits(
+                      GitFileDiffProto.Edit.newBuilder()
+                          .setBeginA(e.beginA())
+                          .setEndA(e.endA())
+                          .setBeginB(e.beginB())
+                          .setEndB(e.endB())));
+      if (gitFileDiff.oldPath().isPresent()) {
+        builder.setOldPath(gitFileDiff.oldPath().get());
+      }
+      if (gitFileDiff.newPath().isPresent()) {
+        builder.setNewPath(gitFileDiff.newPath().get());
+      }
+      if (gitFileDiff.oldMode().isPresent()) {
+        builder.setOldMode(gitFileDiff.oldMode().get().name());
+      }
+      if (gitFileDiff.newMode().isPresent()) {
+        builder.setNewMode(gitFileDiff.newMode().get().name());
+      }
+      if (gitFileDiff.changeType().isPresent()) {
+        builder.setChangeType(gitFileDiff.changeType().get().name());
+      }
+      if (gitFileDiff.patchType().isPresent()) {
+        builder.setPatchType(gitFileDiff.patchType().get().name());
+      }
+      return Protos.toByteArray(builder.build());
     }
 
     @Override
     public GitFileDiff deserialize(byte[] in) {
-      // TODO(ghareeb)
-      return null;
+      ObjectIdConverter idConverter = ObjectIdConverter.create();
+      GitFileDiffProto proto = Protos.parseUnchecked(GitFileDiffProto.parser(), in);
+      GitFileDiff.Builder builder = GitFileDiff.builder();
+      builder
+          .edits(
+              proto.getEditsList().stream()
+                  .map(e -> Edit.create(e.getBeginA(), e.getEndA(), e.getBeginB(), e.getEndB()))
+                  .collect(toImmutableList()))
+          .fileHeader(proto.getFileHeader())
+          .oldId(AbbreviatedObjectId.fromObjectId(idConverter.fromByteString(proto.getOldId())))
+          .newId(AbbreviatedObjectId.fromObjectId(idConverter.fromByteString(proto.getNewId())));
+
+      if (proto.hasField(OLD_PATH_DESCRIPTOR)) {
+        builder.oldPath(Optional.of(proto.getOldPath()));
+      }
+      if (proto.hasField(NEW_PATH_DESCRIPTOR)) {
+        builder.newPath(Optional.of(proto.getNewPath()));
+      }
+      if (proto.hasField(OLD_MODE_DESCRIPTOR)) {
+        builder.oldMode(Optional.of(Patch.FileMode.valueOf(proto.getOldMode())));
+      }
+      if (proto.hasField(NEW_MODE_DESCRIPTOR)) {
+        builder.newMode(Optional.of(Patch.FileMode.valueOf(proto.getNewMode())));
+      }
+      if (proto.hasField(CHANGE_TYPE_DESCRIPTOR)) {
+        builder.changeType(Optional.of(Patch.ChangeType.valueOf(proto.getChangeType())));
+      }
+      if (proto.hasField(PATCH_TYPE_DESCRIPTOR)) {
+        builder.patchType(Optional.of(Patch.PatchType.valueOf(proto.getPatchType())));
+      }
+      return builder.build();
     }
   }
 }
diff --git a/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiffCacheImpl.java b/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiffCacheImpl.java
index 5af6424..97cf37d32 100644
--- a/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiffCacheImpl.java
+++ b/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiffCacheImpl.java
@@ -63,6 +63,7 @@
         persist(GIT_DIFF, GitFileDiffCacheKey.class, GitFileDiff.class)
             .maximumWeight(10 << 20)
             .weigher(GitFileDiffWeigher.class)
+            .keySerializer(GitFileDiffCacheKey.Serializer.INSTANCE)
             .valueSerializer(GitFileDiff.Serializer.INSTANCE)
             .loader(GitFileDiffCacheImpl.Loader.class);
       }
diff --git a/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiffCacheKey.java b/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiffCacheKey.java
index 61250ef..d570ada 100644
--- a/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiffCacheKey.java
+++ b/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiffCacheKey.java
@@ -19,10 +19,13 @@
 import com.google.gerrit.entities.Project.NameKey;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
+import com.google.gerrit.proto.Protos;
+import com.google.gerrit.server.cache.proto.Cache.GitFileDiffKeyProto;
+import com.google.gerrit.server.cache.serialize.CacheSerializer;
+import com.google.gerrit.server.cache.serialize.ObjectIdConverter;
 import com.google.gerrit.server.patch.gitfilediff.GitFileDiffCacheImpl.DiffAlgorithm;
 import org.eclipse.jgit.lib.ObjectId;
 
-// TODO(ghareeb): Implement a key protobuf serializer
 @AutoValue
 public abstract class GitFileDiffCacheKey {
 
@@ -95,4 +98,38 @@
 
     public abstract GitFileDiffCacheKey build();
   }
+
+  public enum Serializer implements CacheSerializer<GitFileDiffCacheKey> {
+    INSTANCE;
+
+    @Override
+    public byte[] serialize(GitFileDiffCacheKey key) {
+      ObjectIdConverter idConverter = ObjectIdConverter.create();
+      return Protos.toByteArray(
+          GitFileDiffKeyProto.newBuilder()
+              .setProject(key.project().get())
+              .setATree(idConverter.toByteString(key.oldTree()))
+              .setBTree(idConverter.toByteString(key.newTree()))
+              .setFilePath(key.newFilePath())
+              .setRenameScore(key.renameScore())
+              .setDiffAlgorithm(key.diffAlgorithm().name())
+              .setWhitepsace(key.whitespace().name())
+              .build());
+    }
+
+    @Override
+    public GitFileDiffCacheKey deserialize(byte[] in) {
+      GitFileDiffKeyProto proto = Protos.parseUnchecked(GitFileDiffKeyProto.parser(), in);
+      ObjectIdConverter idConverter = ObjectIdConverter.create();
+      return GitFileDiffCacheKey.builder()
+          .project(Project.nameKey(proto.getProject()))
+          .oldTree(idConverter.fromByteString(proto.getATree()))
+          .newTree(idConverter.fromByteString(proto.getBTree()))
+          .newFilePath(proto.getFilePath())
+          .renameScore(proto.getRenameScore())
+          .diffAlgorithm(DiffAlgorithm.valueOf(proto.getDiffAlgorithm()))
+          .whitespace(Whitespace.valueOf(proto.getWhitepsace()))
+          .build();
+    }
+  }
 }
diff --git a/javatests/com/google/gerrit/server/cache/serialize/entities/GitFileDiffKeySerializerTest.java b/javatests/com/google/gerrit/server/cache/serialize/entities/GitFileDiffKeySerializerTest.java
new file mode 100644
index 0000000..12d8d00
--- /dev/null
+++ b/javatests/com/google/gerrit/server/cache/serialize/entities/GitFileDiffKeySerializerTest.java
@@ -0,0 +1,49 @@
+//  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.cache.serialize.entities;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
+import com.google.gerrit.server.patch.gitfilediff.GitFileDiffCacheImpl.DiffAlgorithm;
+import com.google.gerrit.server.patch.gitfilediff.GitFileDiffCacheKey;
+import org.eclipse.jgit.lib.ObjectId;
+import org.junit.Test;
+
+public class GitFileDiffKeySerializerTest {
+  private static final ObjectId TREE_ID_1 =
+      ObjectId.fromString("123e9fa8a286255ac7d5ba11b598892735758391");
+  private static final ObjectId TREE_ID_2 =
+      ObjectId.fromString("d07a03a9818c120301cb5b4a969b035479400b5f");
+
+  @Test
+  public void roundTrip() {
+    GitFileDiffCacheKey key =
+        GitFileDiffCacheKey.builder()
+            .project(Project.nameKey("project/x"))
+            .oldTree(TREE_ID_1)
+            .newTree(TREE_ID_2)
+            .newFilePath("some_file.txt")
+            .renameScore(65)
+            .diffAlgorithm(DiffAlgorithm.HISTOGRAM)
+            .whitespace(Whitespace.IGNORE_ALL)
+            .build();
+
+    byte[] serialized = GitFileDiffCacheKey.Serializer.INSTANCE.serialize(key);
+
+    assertThat(GitFileDiffCacheKey.Serializer.INSTANCE.deserialize(serialized)).isEqualTo(key);
+  }
+}
diff --git a/javatests/com/google/gerrit/server/cache/serialize/entities/GitFileDiffSerializerTest.java b/javatests/com/google/gerrit/server/cache/serialize/entities/GitFileDiffSerializerTest.java
new file mode 100644
index 0000000..baba43d
--- /dev/null
+++ b/javatests/com/google/gerrit/server/cache/serialize/entities/GitFileDiffSerializerTest.java
@@ -0,0 +1,59 @@
+// 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.cache.serialize.entities;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.entities.Patch.ChangeType;
+import com.google.gerrit.entities.Patch.FileMode;
+import com.google.gerrit.entities.Patch.PatchType;
+import com.google.gerrit.server.patch.entities.Edit;
+import com.google.gerrit.server.patch.gitfilediff.GitFileDiff;
+import com.google.gerrit.server.patch.gitfilediff.GitFileDiff.Serializer;
+import java.util.Optional;
+import org.eclipse.jgit.lib.AbbreviatedObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.junit.Test;
+
+public class GitFileDiffSerializerTest {
+  private static final ObjectId OLD_ID =
+      ObjectId.fromString("123e9fa8a286255ac7d5ba11b598892735758391");
+  private static final ObjectId NEW_ID =
+      ObjectId.fromString("d07a03a9818c120301cb5b4a969b035479400b5f");
+
+  @Test
+  public void roundTrip() {
+    ImmutableList<Edit> edits =
+        ImmutableList.of(Edit.create(1, 5, 3, 4), Edit.create(21, 30, 150, 158));
+
+    GitFileDiff gitFileDiff =
+        GitFileDiff.builder()
+            .edits(edits)
+            .fileHeader("file_header")
+            .oldPath(Optional.of("old_file_path.txt"))
+            .newPath(Optional.empty())
+            .oldId(AbbreviatedObjectId.fromObjectId(OLD_ID))
+            .newId(AbbreviatedObjectId.fromObjectId(NEW_ID))
+            .changeType(Optional.of(ChangeType.DELETED))
+            .patchType(Optional.of(PatchType.UNIFIED))
+            .oldMode(Optional.of(FileMode.REGULAR_FILE))
+            .newMode(Optional.of(FileMode.REGULAR_FILE))
+            .build();
+
+    byte[] serialized = Serializer.INSTANCE.serialize(gitFileDiff);
+    assertThat(Serializer.INSTANCE.deserialize(serialized)).isEqualTo(gitFileDiff);
+  }
+}
diff --git a/proto/cache.proto b/proto/cache.proto
index aa71b87..4dddc3a 100644
--- a/proto/cache.proto
+++ b/proto/cache.proto
@@ -561,3 +561,37 @@
 message ModifiedFilesProto {
   repeated ModifiedFileProto modifiedFile = 1;
 }
+
+// Serialized form of a collection of
+// com.google.gerrit.server.patch.gitfilediff.GitFileDiffCacheImpl.Key
+// Next ID: 8
+message GitFileDiffKeyProto {
+  string project = 1;
+  bytes a_tree = 2;
+  bytes b_tree = 3;
+  string file_path = 4;
+  int32 rename_score = 5;
+  string diff_algorithm = 6; // ENUM as string
+  string whitepsace = 7; // ENUM as string
+}
+
+// Serialized form of com.google.gerrit.server.patch.gitfilediff.GitFileDiff
+// Next ID: 11
+message GitFileDiffProto {
+  message Edit {
+    int32 begin_a = 1;
+    int32 end_a = 2;
+    int32 begin_b = 3;
+    int32 end_b = 4;
+  }
+  repeated Edit edits = 1;
+  string file_header = 2;
+  string old_path = 3;
+  string new_path = 4;
+  bytes old_id = 5;
+  bytes new_id = 6;
+  string old_mode = 7; // ENUM as string
+  string new_mode = 8; // ENUM as string
+  string change_type = 9; // ENUM as string
+  string patch_type = 10; // ENUM as string
+}