Merge "Use Diffy as avatar for the Gerrit server itself"
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java
index b10637a..55fb55b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java
@@ -187,7 +187,7 @@
           lines.add(new SkippedLine(lastA, lastB, b.size() - lastB));
         }
       }
-    }else{
+    } else {
       // Display the patch header for binary
       for (final String line : script.getPatchHeader()) {
         appendFileHeader(nc, line);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedDiffTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedDiffTable.java
index a4a089e..be1846f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedDiffTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedDiffTable.java
@@ -226,94 +226,18 @@
       appendFileHeader(nc, line);
     }
     final ArrayList<PatchLine> lines = new ArrayList<PatchLine>();
-    if (!isDisplayBinary) {
-      final SparseHtmlFile a = getSparseHtmlFileA(script);
-      final SparseHtmlFile b = getSparseHtmlFileB(script);
+
+    if (hasDifferences(script)) {
       if (script.getDisplayMethodA() == DisplayMethod.IMG
           || script.getDisplayMethodB() == DisplayMethod.IMG) {
-        final String rawBase = GWT.getHostPageBaseURL() + "cat/";
-
-        nc.openTr();
-        nc.setAttribute("valign", "center");
-        nc.setAttribute("align", "center");
-
-        nc.openTd();
-        nc.nbsp();
-        nc.closeTd();
-
-        nc.openTd();
-        nc.nbsp();
-        nc.closeTd();
-
-        nc.openTd();
-        nc.nbsp();
-        nc.closeTd();
-
-        nc.openTd();
-        if (script.getDisplayMethodA() == DisplayMethod.IMG) {
-          if (idSideA == null) {
-            appendImgTag(nc, rawBase + KeyUtil.encode(patchKey.toString()) + "^1");
-          } else {
-            Patch.Key k = new Patch.Key(idSideA, patchKey.get());
-            appendImgTag(nc, rawBase + KeyUtil.encode(k.toString()) + "^0");
-          }
-        }
-        if (script.getDisplayMethodB() == DisplayMethod.IMG) {
-          appendImgTag(nc, rawBase + KeyUtil.encode(patchKey.toString()) + "^0");
-        }
-        nc.closeTd();
-
-        nc.closeTr();
+        appendImageDifferences(script, nc);
+      } else if (!isDisplayBinary) {
+        appendTextDifferences(script, nc, lines);
       }
-
-      if (hasDifferences(script)) {
-        final boolean syntaxHighlighting =
-            script.getDiffPrefs().isSyntaxHighlighting();
-        for (final EditList.Hunk hunk : script.getHunks()) {
-          appendHunkHeader(nc, hunk);
-          while (hunk.next()) {
-            if (hunk.isContextLine()) {
-              openLine(nc);
-              appendLineNumberForSideA(nc, hunk.getCurA());
-              appendLineNumberForSideB(nc, hunk.getCurB());
-              appendLineText(nc, false, CONTEXT, a, hunk.getCurA());
-              closeLine(nc);
-              hunk.incBoth();
-              lines.add(new PatchLine(CONTEXT, hunk.getCurA(), hunk.getCurB()));
-
-            } else if (hunk.isDeletedA()) {
-              openLine(nc);
-              appendLineNumberForSideA(nc, hunk.getCurA());
-              padLineNumberForSideB(nc);
-              appendLineText(nc, syntaxHighlighting, DELETE, a, hunk.getCurA());
-              closeLine(nc);
-              hunk.incA();
-              lines.add(new PatchLine(DELETE, hunk.getCurA(), -1));
-              if (a.size() == hunk.getCurA()
-                  && script.getA().isMissingNewlineAtEnd()) {
-                appendNoLF(nc);
-              }
-
-            } else if (hunk.isInsertedB()) {
-              openLine(nc);
-              padLineNumberForSideA(nc);
-              appendLineNumberForSideB(nc, hunk.getCurB());
-              appendLineText(nc, syntaxHighlighting, INSERT, b, hunk.getCurB());
-              closeLine(nc);
-              hunk.incB();
-              lines.add(new PatchLine(INSERT, -1, hunk.getCurB()));
-              if (b.size() == hunk.getCurB()
-                  && script.getB().isMissingNewlineAtEnd()) {
-                appendNoLF(nc);
-              }
-            }
-          }
-        }
-      }
-    }
-    if (!hasDifferences(script)) {
+    } else {
       appendNoDifferences(nc);
     }
+
     resetHtml(nc);
     populateTableHeader(script, detail);
     if (hasDifferences(script)) {
@@ -347,6 +271,102 @@
     }
   }
 
+  private void appendImageLine(final SafeHtmlBuilder nc, final String url,
+      final boolean syntaxHighlighting, final boolean isInsert) {
+    nc.openTr();
+    nc.setAttribute("valign", "center");
+    nc.setAttribute("align", "center");
+
+    nc.openTd();
+    nc.setStyleName(Gerrit.RESOURCES.css().iconCell());
+    nc.closeTd();
+
+    padLineNumberForSideA(nc);
+    padLineNumberForSideB(nc);
+
+    nc.openTd();
+    nc.setStyleName(Gerrit.RESOURCES.css().fileLine());
+    if (isInsert) {
+      setStyleInsert(nc, syntaxHighlighting);
+    } else {
+      setStyleDelete(nc, syntaxHighlighting);
+    }
+    appendImgTag(nc, url);
+    nc.closeTd();
+
+    nc.closeTr();
+  }
+
+  private void appendImageDifferences(final PatchScript script,
+      final SafeHtmlBuilder nc) {
+    final boolean syntaxHighlighting =
+        script.getDiffPrefs().isSyntaxHighlighting();
+    final String rawBase = GWT.getHostPageBaseURL() + "cat/";
+
+    if (script.getDisplayMethodA() == DisplayMethod.IMG) {
+      final String url;
+      if (idSideA == null) {
+        url = rawBase + KeyUtil.encode(patchKey.toString()) + "^1";
+      } else {
+        Patch.Key k = new Patch.Key(idSideA, patchKey.get());
+        url = rawBase + KeyUtil.encode(k.toString()) + "^0";
+      }
+      appendImageLine(nc, url, syntaxHighlighting, false);
+    }
+    if (script.getDisplayMethodB() == DisplayMethod.IMG) {
+      final String url = rawBase + KeyUtil.encode(patchKey.toString()) + "^0";
+      appendImageLine(nc, url, syntaxHighlighting, true);
+    }
+  }
+
+  private void appendTextDifferences(final PatchScript script,
+      final SafeHtmlBuilder nc, final ArrayList<PatchLine> lines) {
+    final SparseHtmlFile a = getSparseHtmlFileA(script);
+    final SparseHtmlFile b = getSparseHtmlFileB(script);
+    final boolean syntaxHighlighting =
+        script.getDiffPrefs().isSyntaxHighlighting();
+    for (final EditList.Hunk hunk : script.getHunks()) {
+      appendHunkHeader(nc, hunk);
+      while (hunk.next()) {
+        if (hunk.isContextLine()) {
+          openLine(nc);
+          appendLineNumberForSideA(nc, hunk.getCurA());
+          appendLineNumberForSideB(nc, hunk.getCurB());
+          appendLineText(nc, false, CONTEXT, a, hunk.getCurA());
+          closeLine(nc);
+          hunk.incBoth();
+          lines.add(new PatchLine(CONTEXT, hunk.getCurA(), hunk.getCurB()));
+
+        } else if (hunk.isDeletedA()) {
+          openLine(nc);
+          appendLineNumberForSideA(nc, hunk.getCurA());
+          padLineNumberForSideB(nc);
+          appendLineText(nc, syntaxHighlighting, DELETE, a, hunk.getCurA());
+          closeLine(nc);
+          hunk.incA();
+          lines.add(new PatchLine(DELETE, hunk.getCurA(), -1));
+          if (a.size() == hunk.getCurA()
+              && script.getA().isMissingNewlineAtEnd()) {
+            appendNoLF(nc);
+          }
+
+        } else if (hunk.isInsertedB()) {
+          openLine(nc);
+          padLineNumberForSideA(nc);
+          appendLineNumberForSideB(nc, hunk.getCurB());
+          appendLineText(nc, syntaxHighlighting, INSERT, b, hunk.getCurB());
+          closeLine(nc);
+          hunk.incB();
+          lines.add(new PatchLine(INSERT, -1, hunk.getCurB()));
+          if (b.size() == hunk.getCurB()
+              && script.getB().isMissingNewlineAtEnd()) {
+            appendNoLF(nc);
+          }
+        }
+      }
+    }
+  }
+
   @Override
   public void display(final CommentDetail cd, boolean expandComments) {
     if (cd.isEmpty()) {
@@ -519,6 +539,22 @@
     }
   }
 
+  private void setStyleDelete(final SafeHtmlBuilder m,
+      boolean syntaxHighlighting) {
+    m.addStyleName(Gerrit.RESOURCES.css().diffTextDELETE());
+    if (syntaxHighlighting) {
+      m.addStyleName(Gerrit.RESOURCES.css().fileLineDELETE());
+    }
+  }
+
+  private void setStyleInsert(final SafeHtmlBuilder m,
+      boolean syntaxHighlighting) {
+    m.addStyleName(Gerrit.RESOURCES.css().diffTextINSERT());
+    if (syntaxHighlighting) {
+      m.addStyleName(Gerrit.RESOURCES.css().fileLineINSERT());
+    }
+  }
+
   private void appendLineText(final SafeHtmlBuilder m,
       boolean syntaxHighlighting, final PatchLine.Type type,
       final SparseHtmlFile src, final int i) {
@@ -533,18 +569,12 @@
         m.append(text);
         break;
       case DELETE:
-        m.addStyleName(Gerrit.RESOURCES.css().diffTextDELETE());
-        if (syntaxHighlighting) {
-          m.addStyleName(Gerrit.RESOURCES.css().fileLineDELETE());
-        }
+        setStyleDelete(m, syntaxHighlighting);
         m.append("-");
         m.append(text);
         break;
       case INSERT:
-        m.addStyleName(Gerrit.RESOURCES.css().diffTextINSERT());
-        if (syntaxHighlighting) {
-          m.addStyleName(Gerrit.RESOURCES.css().fileLineINSERT());
-        }
+        setStyleInsert(m, syntaxHighlighting);
         m.append("+");
         m.append(text);
         break;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/MimeUtilFileTypeRegistry.java b/gerrit-server/src/main/java/com/google/gerrit/server/MimeUtilFileTypeRegistry.java
index 792c1e7..b271d6a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/MimeUtilFileTypeRegistry.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/MimeUtilFileTypeRegistry.java
@@ -60,6 +60,38 @@
     mimeUtil.registerMimeDetector(name);
   }
 
+
+  /**
+   * Get specificity of mime types with generic types forced to low values
+   *
+   * "application/octet-stream" is forced to -1.
+   * "text/plain" is forced to 0.
+   * All other mime types return the specificity reported by mimeType itself.
+   *
+   * @param mimeType The mimeType to get the corrected specificity for.
+   * @return The corrected specificity.
+   */
+  private int getCorrectedMimeSpecificity(MimeType mimeType) {
+    // Although the documentation of MimeType's getSpecificity claims that for
+    // example "application/octet-stream" always has a specificity of 0, it
+    // effectively returns 1 for us. This causes problems when trying to get
+    // the correct mime type via sorting. For example in
+    // [application/octet-stream, image/x-icon] both mime types come with
+    // specificity 1 for us. Hence, getMimeType below may end up using
+    // application/octet-stream instead of the more specific image/x-icon.
+    // Therefore, we have to force the specificity of generic types below the
+    // default of 1.
+    //
+    final String mimeTypeStr = mimeType.toString();
+    if (mimeTypeStr.equals("application/octet-stream")) {
+      return -1;
+    }
+    if (mimeTypeStr.equals("text/plain")) {
+      return 0;
+    }
+    return mimeType.getSpecificity();
+  }
+
   @SuppressWarnings("unchecked")
   public MimeType getMimeType(final String path, final byte[] content) {
     Set<MimeType> mimeTypes = new HashSet<MimeType>();
@@ -84,7 +116,7 @@
     Collections.sort(types, new Comparator<MimeType>() {
       @Override
       public int compare(MimeType a, MimeType b) {
-        return b.getSpecificity() - a.getSpecificity();
+        return getCorrectedMimeSpecificity(b) - getCorrectedMimeSpecificity(a);
       }
     });
     return types.get(0);