public PagedResult<DiffComment> readDiffComments(String source) throws ReviewboardException {

    try {
      JSONObject object = checkedGetJSonRootObject(source);

      int totalResults = object.getInt("total_results");
      JSONArray jsonDiffComments = object.getJSONArray("diff_comments");

      List<DiffComment> diffComments = new ArrayList<DiffComment>();
      for (int i = 0; i < jsonDiffComments.length(); i++) {
        JSONObject jsonDiffComment = jsonDiffComments.getJSONObject(i);
        DiffComment comment = new DiffComment();

        mapComment(jsonDiffComment, comment);
        comment.setFirstLine(jsonDiffComment.getInt("first_line"));
        comment.setNumLines(jsonDiffComment.getInt("num_lines"));
        String fileHref =
            jsonDiffComment.getJSONObject("links").getJSONObject("filediff").getString("href");
        int fileId = Integer.parseInt(fileHref.replaceFirst(".*files/", "").replace("/", ""));
        comment.setFileId(fileId);

        diffComments.add(comment);
      }

      return PagedResult.create(diffComments, totalResults);

    } catch (JSONException e) {
      throw new ReviewboardException(e.getMessage(), e);
    }
  }
  public void addDiff(
      Diff diff, List<FileDiff> fileDiffs, Multimap<Integer, DiffComment> fileIdToDiffComments) {

    Assert.isNotNull(diff, "diff may not be null");
    TaskAttribute diffsAttribute = taskData.getRoot().getAttribute(DIFFS.toString());
    TaskAttribute diffAttribute = diffsAttribute.createAttribute(PREFIX_DIFF + diff.getRevision());
    diffAttribute.setValue(String.valueOf(diff.getRevision()));
    taskData
        .getAttributeMapper()
        .setDateValue(diffAttribute.createAttribute(LAST_UPDATED.toString()), diff.getTimestamp());
    int totalPublicComments = 0;
    int totalDraftComments = 0;

    for (FileDiff fileDiff : fileDiffs) {

      Collection<DiffComment> diffComments = fileIdToDiffComments.get(fileDiff.getId());
      int draftComments = 0;
      int publicComments = 0;
      for (DiffComment diffComment : diffComments) {
        if (Boolean.FALSE.equals(diffComment.getPublic())) draftComments++;
        else publicComments++;
      }
      totalPublicComments += publicComments;
      totalDraftComments += draftComments;

      TaskAttribute fileDiffAttribute =
          diffAttribute.createAttribute(PREFIX_FILE + fileDiff.getId());
      fileDiffAttribute.setValue(String.valueOf(fileDiff.getId()));
      fileDiffAttribute.createAttribute(SOURCE_FILE.toString()).setValue(fileDiff.getSourceFile());
      fileDiffAttribute
          .createAttribute(DEST_FILE.toString())
          .setValue(fileDiff.getDestinationFile());
      fileDiffAttribute
          .createAttribute(SOURCE_REVISION.toString())
          .setValue(fileDiff.getSourceRevision());
      fileDiffAttribute
          .createAttribute(DEST_DETAIL.toString())
          .setValue(fileDiff.getDestinationDetail());
      fileDiffAttribute
          .createAttribute(NUM_PUBLIC_COMMENTS.toString())
          .setValue(String.valueOf(publicComments));
      fileDiffAttribute
          .createAttribute(NUM_DRAFT_COMMENTS.toString())
          .setValue(String.valueOf(draftComments));
    }

    diffAttribute
        .createAttribute(NUM_PUBLIC_COMMENTS.toString())
        .setValue(String.valueOf(totalPublicComments));
    diffAttribute
        .createAttribute(NUM_DRAFT_COMMENTS.toString())
        .setValue(String.valueOf(totalDraftComments));
  }
  private DiffComment mapDiffComment(JSONObject jsonDiffComment) throws JSONException {

    DiffComment comment = new DiffComment();

    mapComment(jsonDiffComment, comment);
    comment.setFirstLine(jsonDiffComment.getInt("first_line"));
    comment.setNumLines(jsonDiffComment.getInt("num_lines"));
    String fileHref =
        jsonDiffComment.getJSONObject("links").getJSONObject("filediff").getString("href");
    int fileId = Integer.parseInt(fileHref.replaceFirst(".*files/", "").replace("/", ""));
    comment.setFileId(fileId);

    return comment;
  }