Reduce DOM nodes when updating reviewed status

There appears to be a memory leak associated with replacing an
existing Image widget in the hidden file list table. I think GWT is
not completely discarding the old checkmark Image when its replaced
by a new checkmark Image if the user re-reviews a file within the
patch set.

Minimize the leak by only making DOM changes if there really is a
modification in reviewed status.

Improve lookup performance on large patch sets by indexing all
Patch objects using a HashMap. This makes lookup for marking the
reviewed flag or updating draft counts to be O(1) rather than O(N),
which can help on larger patch sets.

Change-Id: I5530422bfb6b23cd5a990ac24f92e9f125dc7a1e
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchTable.java
index d00ef89..d47e2c1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchTable.java
@@ -48,7 +48,9 @@
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 public class PatchTable extends Composite {
   public interface PatchValidator {
@@ -80,6 +82,7 @@
   private String savePointerId;
   private PatchSet.Id base;
   private List<Patch> patchList;
+  private Map<Patch.Key, Integer> patchMap;
   private ListenableAccountDiffPreference listenablePrefs;
 
   private List<ClickHandler> clickHandlers;
@@ -97,18 +100,25 @@
   }
 
   public int indexOf(Patch.Key patch) {
-    for (int i = 0; i < patchList.size(); i++) {
-      if (patchList.get(i).getKey().equals(patch)) {
-        return i;
+    Integer i = patchMap().get(patch);
+    return i != null ? i : -1;
+  }
+
+  private Map<Key, Integer> patchMap() {
+    if (patchMap == null) {
+      patchMap = new HashMap<Patch.Key, Integer>();
+      for (int i = 0; i < patchList.size(); i++) {
+        patchMap.put(patchList.get(i).getKey(), i);
       }
     }
-    return -1;
+    return patchMap;
   }
 
   public void display(PatchSet.Id base, PatchSetDetail detail) {
     this.base = base;
     this.detail = detail;
     this.patchList = detail.getPatches();
+    this.patchMap = null;
     myTable = null;
 
     final DisplayCommand cmd = new DisplayCommand(patchList, base);
@@ -326,36 +336,33 @@
     }
 
     void updateReviewedStatus(final Patch.Key patchKey, boolean reviewed) {
-      final int row = findRow(patchKey);
-      if (0 <= row) {
-        final Patch patch = getRowItem(row);
-        if (patch != null) {
-          patch.setReviewedByCurrentUser(reviewed);
-
+      int idx = patchMap().get(patchKey);
+      if (0 <= idx) {
+        Patch patch = patchList.get(idx);
+        if (patch.isReviewedByCurrentUser() != reviewed) {
+          int row = idx + 1;
           int col = C_SIDEBYSIDE + 2;
           if (patch.getPatchType() == Patch.PatchType.BINARY) {
             col = C_SIDEBYSIDE + 3;
           }
-
           if (reviewed) {
             table.setWidget(row, col, new Image(Gerrit.RESOURCES.greenCheck()));
           } else {
             table.clearCell(row, col);
           }
+          patch.setReviewedByCurrentUser(reviewed);
         }
       }
     }
 
     void notifyDraftDelta(final Patch.Key key, final int delta) {
-      final int row = findRow(key);
-      if (0 <= row) {
-        final Patch p = getRowItem(row);
-        if (p != null) {
-          p.setDraftCount(p.getDraftCount() + delta);
-          final SafeHtmlBuilder m = new SafeHtmlBuilder();
-          appendCommentCount(m, p);
-          SafeHtml.set(table, row, C_DRAFT, m);
-        }
+      int idx = patchMap().get(key);
+      if (0 <= idx) {
+        Patch p = patchList.get(idx);
+        p.setDraftCount(p.getDraftCount() + delta);
+        SafeHtmlBuilder m = new SafeHtmlBuilder();
+        appendCommentCount(m, p);
+        SafeHtml.set(table, idx + 1, C_DRAFT, m);
       }
     }