/**
  * Writes a line spanning the full width of the code view, including the gutter.
  *
  * @param text to put on that line; will be HTML-escaped.
  */
 private void writeFullWidthLine(String text) {
   try {
     os.write("<tr><td class='diff-cell' colspan='4'>".getBytes());
     os.write(StringUtils.escapeForHtml(text, false).getBytes());
     os.write("</td></tr>\n".getBytes());
   } catch (IOException ex) {
     // Cannot happen with a ByteArrayOutputStream
   }
 }
  protected void writeRange(final char prefix, final int begin, final int cnt) throws IOException {
    os.write(' ');
    os.write(prefix);
    switch (cnt) {
      case 0:
        // If the range is empty, its beginning number must be the
        // line just before the range, or 0 if the range is at the
        // start of the file stream. Here, begin is always 1 based,
        // so an empty file would produce "0,0".
        //
        os.write(encodeASCII(begin - 1));
        os.write(',');
        os.write('0');
        break;

      case 1:
        // If the range is exactly one line, produce only the number.
        //
        os.write(encodeASCII(begin));
        break;

      default:
        os.write(encodeASCII(begin));
        os.write(',');
        os.write(encodeASCII(cnt));
        break;
    }
  }
 @Override
 public void flush() throws IOException {
   if (truncated) {
     os.resetTo(truncateTo);
   }
   super.flush();
 }
 /** Rewind and issue a message that the diff is too large. */
 private void reset() {
   if (!isOff) {
     os.resetTo(startCurrent);
     writeFullWidthLine(getMsg("gb.diffFileDiffTooLarge", "Diff too large"));
     totalNofLinesCurrent = totalNofLinesPrevious;
     isOff = true;
   }
 }
 @Override
 public void format(DiffEntry ent) throws IOException {
   currentPath = diffStat.addPath(ent);
   nofLinesCurrent = 0;
   isOff = false;
   entry = ent;
   if (!truncated) {
     totalNofLinesPrevious = totalNofLinesCurrent;
     if (globalDiffLimit > 0 && totalNofLinesPrevious > globalDiffLimit) {
       truncated = true;
       isOff = true;
     }
     truncateTo = os.size();
   } else {
     isOff = true;
   }
   if (truncated) {
     skipped.add(ent);
   } else {
     // Produce a header here and now
     String path;
     String id;
     if (ChangeType.DELETE.equals(ent.getChangeType())) {
       path = ent.getOldPath();
       id = ent.getOldId().name();
     } else {
       path = ent.getNewPath();
       id = ent.getNewId().name();
     }
     StringBuilder sb =
         new StringBuilder(
             MessageFormat.format(
                 "<div class='header'><div class=\"diffHeader\" id=\"n{0}\"><i class=\"icon-file\"></i> ",
                 id));
     sb.append(StringUtils.escapeForHtml(path, false)).append("</div></div>");
     sb.append("<div class=\"diff\"><table cellpadding='0'><tbody>\n");
     os.write(sb.toString().getBytes());
   }
   // Keep formatting, but if off, don't produce anything anymore. We just keep on counting.
   super.format(ent);
   if (!truncated) {
     // Close the table
     os.write("</tbody></table></div>\n".getBytes());
   }
 }
 /**
  * Output a hunk header
  *
  * @param aStartLine within first source
  * @param aEndLine within first source
  * @param bStartLine within second source
  * @param bEndLine within second source
  * @throws IOException
  */
 @Override
 protected void writeHunkHeader(int aStartLine, int aEndLine, int bStartLine, int bEndLine)
     throws IOException {
   if (nofLinesCurrent++ == 0) {
     handleChange();
     startCurrent = os.size();
   }
   if (!isOff) {
     totalNofLinesCurrent++;
     if (nofLinesCurrent > maxDiffLinesPerFile && maxDiffLinesPerFile > 0) {
       reset();
     } else {
       os.write(
           "<tr><th class='diff-line' data-lineno='..'></th><th class='diff-line' data-lineno='..'></th><th class='diff-state'></th><td class='hunk_header'>"
               .getBytes());
       os.write('@');
       os.write('@');
       writeRange('-', aStartLine + 1, aEndLine - aStartLine);
       writeRange('+', bStartLine + 1, bEndLine - bStartLine);
       os.write(' ');
       os.write('@');
       os.write('@');
       os.write("</td></tr>\n".getBytes());
     }
   }
   left = aStartLine + 1;
   right = bStartLine + 1;
 }
 /**
  * Workaround function for complex private methods in DiffFormatter. This sets the html for the
  * diff headers.
  *
  * @return
  */
 public String getHtml() {
   String html = RawParseUtils.decode(os.toByteArray());
   String[] lines = html.split("\n");
   StringBuilder sb = new StringBuilder();
   for (String line : lines) {
     if (line.startsWith("index")) {
       // skip index lines
     } else if (line.startsWith("new file") || line.startsWith("deleted file")) {
       // skip new file lines
     } else if (line.startsWith("\\ No newline")) {
       // skip no new line
     } else if (line.startsWith("---") || line.startsWith("+++")) {
       // skip --- +++ lines
     } else if (line.startsWith("diff")) {
       // skip diff lines
     } else {
       boolean gitLinkDiff =
           line.length() > 0 && line.substring(1).startsWith("Subproject commit");
       if (gitLinkDiff) {
         sb.append("<tr><th class='diff-line'></th><th class='diff-line'></th>");
         if (line.charAt(0) == '+') {
           sb.append("<th class='diff-state diff-state-add'></th><td class=\"diff-cell add2\">");
         } else {
           sb.append(
               "<th class='diff-state diff-state-sub'></th><td class=\"diff-cell remove2\">");
         }
         line = StringUtils.escapeForHtml(line.substring(1), false);
       }
       sb.append(line);
       if (gitLinkDiff) {
         sb.append("</td></tr>");
       }
       sb.append('\n');
     }
   }
   if (truncated) {
     sb.append(
         MessageFormat.format(
             "<div class='header'><div class='diffHeader'>{0}</div></div>",
             StringUtils.escapeForHtml(
                 getMsg("gb.diffTruncated", "Diff truncated after the above file"), false)));
     // List all files not shown. We can be sure we do have at least one path in skipped.
     sb.append(
         "<div class='diff'><table cellpadding='0'><tbody><tr><td class='diff-cell' colspan='4'>");
     String deletedSuffix =
         StringUtils.escapeForHtml(getMsg("gb.diffDeletedFileSkipped", "(deleted)"), false);
     boolean first = true;
     for (DiffEntry entry : skipped) {
       if (!first) {
         sb.append('\n');
       }
       if (ChangeType.DELETE.equals(entry.getChangeType())) {
         sb.append(
             "<span id=\"n"
                 + entry.getOldId().name()
                 + "\">"
                 + StringUtils.escapeForHtml(entry.getOldPath(), false)
                 + ' '
                 + deletedSuffix
                 + "</span>");
       } else {
         sb.append(
             "<span id=\"n"
                 + entry.getNewId().name()
                 + "\">"
                 + StringUtils.escapeForHtml(entry.getNewPath(), false)
                 + "</span>");
       }
       first = false;
     }
     skipped.clear();
     sb.append("</td></tr></tbody></table></div>");
   }
   return sb.toString();
 }
 @Override
 protected void writeLine(final char prefix, final RawText text, final int cur)
     throws IOException {
   if (nofLinesCurrent++ == 0) {
     handleChange();
     startCurrent = os.size();
   }
   // update entry diffstat
   currentPath.update(prefix);
   if (isOff) {
     return;
   }
   totalNofLinesCurrent++;
   if (nofLinesCurrent > maxDiffLinesPerFile && maxDiffLinesPerFile > 0) {
     reset();
   } else {
     // output diff
     os.write("<tr>".getBytes());
     switch (prefix) {
       case '+':
         os.write(
             ("<th class='diff-line'></th><th class='diff-line' data-lineno='"
                     + (right++)
                     + "'></th>")
                 .getBytes());
         os.write("<th class='diff-state diff-state-add'></th>".getBytes());
         os.write("<td class='diff-cell add2'>".getBytes());
         break;
       case '-':
         os.write(
             ("<th class='diff-line' data-lineno='"
                     + (left++)
                     + "'></th><th class='diff-line'></th>")
                 .getBytes());
         os.write("<th class='diff-state diff-state-sub'></th>".getBytes());
         os.write("<td class='diff-cell remove2'>".getBytes());
         break;
       default:
         os.write(
             ("<th class='diff-line' data-lineno='"
                     + (left++)
                     + "'></th><th class='diff-line' data-lineno='"
                     + (right++)
                     + "'></th>")
                 .getBytes());
         os.write("<th class='diff-state'></th>".getBytes());
         os.write("<td class='diff-cell context2'>".getBytes());
         break;
     }
     os.write(encode(codeLineToHtml(prefix, text.getString(cur))));
     os.write("</td></tr>\n".getBytes());
   }
 }