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 PagedResult<ScreenshotComment> readScreenshotComments(String source)
      throws ReviewboardException {

    try {
      JSONObject object = checkedGetJSonRootObject(source);

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

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

        mapComment(jsonDiffComment, comment);

        diffComments.add(comment);
      }

      return PagedResult.create(diffComments, totalResults);

    } catch (JSONException e) {
      throw new ReviewboardException(e.getMessage(), e);
    }
  }
  public PagedResult<FileDiff> readFileDiffs(String source) throws ReviewboardException {
    try {
      JSONObject json = checkedGetJSonRootObject(source);

      int totalResults = json.getInt("total_results");
      JSONArray jsonDiffs = json.getJSONArray("files");

      List<FileDiff> diffList = new ArrayList<FileDiff>();
      for (int i = 0; i < jsonDiffs.length(); i++) {

        JSONObject jsonDiff = jsonDiffs.getJSONObject(i);
        int id = jsonDiff.getInt("id");
        String sourceFile = jsonDiff.getString("source_file");
        String sourceRevision = jsonDiff.getString("source_revision");
        String destinationFile = jsonDiff.getString("dest_file");
        String destinationDetail = jsonDiff.getString("dest_detail");

        diffList.add(
            new FileDiff(id, sourceFile, sourceRevision, destinationFile, destinationDetail));
      }

      return PagedResult.create(diffList, totalResults);

    } catch (JSONException e) {
      throw new ReviewboardException(e.getMessage(), e);
    }
  }
  public PagedResult<ReviewReply> readReviewReplies(String source) throws ReviewboardException {

    try {
      JSONObject rootObject = checkedGetJSonRootObject(source);

      int totalResults = rootObject.getInt("total_results");

      JSONArray jsonReplies = rootObject.getJSONArray("replies");
      List<ReviewReply> replies = new ArrayList<ReviewReply>();

      for (int i = 0; i < jsonReplies.length(); i++) {

        JSONObject jsonReply = jsonReplies.getJSONObject(i);

        ReviewReply reply = new ReviewReply();
        reply.setId(jsonReply.getInt("id"));
        reply.setBodyTop(jsonReply.getString("body_top"));
        reply.setBodyBottom(jsonReply.getString("body_bottom"));
        reply.setPublicReply(jsonReply.getBoolean("public"));
        reply.setTimestamp(ReviewboardUtil.marshallDate(jsonReply.getString("timestamp")));
        reply.setUser(jsonReply.getJSONObject("links").getJSONObject("user").getString("title"));

        replies.add(reply);
      }

      return PagedResult.create(replies, totalResults);
    } catch (JSONException e) {
      throw new ReviewboardException(e.getMessage(), e);
    }
  }
  public PagedResult<Repository> readRepositories(String source) throws ReviewboardException {

    try {
      JSONObject rootObject = checkedGetJSonRootObject(source);

      int totalResults = rootObject.getInt("total_results");

      JSONArray jsonRepositories = rootObject.getJSONArray("repositories");

      List<Repository> repositories = new ArrayList<Repository>();

      for (int i = 0; i < jsonRepositories.length(); i++) {

        JSONObject jsonRepository = jsonRepositories.getJSONObject(i);

        Repository repository = new Repository();
        repository.setId(jsonRepository.getInt("id"));
        repository.setName(jsonRepository.getString("name"));
        repository.setTool(RepositoryType.fromDisplayName(jsonRepository.getString("tool")));
        repository.setPath(jsonRepository.getString("path"));

        repositories.add(repository);
      }

      return PagedResult.create(repositories, totalResults);
    } catch (JSONException e) {
      throw new ReviewboardException(e.getMessage(), e);
    }
  }
  public PagedResult<ReviewGroup> readGroups(String source) throws ReviewboardException {
    try {
      JSONObject rootObject = checkedGetJSonRootObject(source);
      JSONArray jsonGroups = rootObject.getJSONArray("groups");
      int totalResults = rootObject.getInt("total_results");

      List<ReviewGroup> groups = new ArrayList<ReviewGroup>();
      for (int i = 0; i < jsonGroups.length(); i++) {

        JSONObject jsonObject = jsonGroups.getJSONObject(i);

        ReviewGroup group = new ReviewGroup();
        group.setId(jsonObject.getInt("id"));
        group.setName(jsonObject.getString("name"));
        group.setDisplayName(jsonObject.getString("display_name"));
        group.setUrl(jsonObject.getString("url"));
        group.setMailingList(jsonObject.getString("mailing_list"));

        groups.add(group);
      }

      return PagedResult.create(groups, totalResults);
    } catch (JSONException e) {
      throw new ReviewboardException(e.getMessage(), e);
    }
  }
  public PagedResult<User> readUsers(String source) throws ReviewboardException {

    try {
      JSONObject rootObject = checkedGetJSonRootObject(source);

      int totalResults = rootObject.getInt("total_results");

      JSONArray jsonUsers = rootObject.getJSONArray("users");
      List<User> users = new ArrayList<User>();

      for (int i = 0; i < jsonUsers.length(); i++) {

        JSONObject jsonUser = jsonUsers.getJSONObject(i);

        User user = new User();
        user.setId(jsonUser.getInt("id"));
        user.setUrl(jsonUser.getString("url"));
        user.setUsername(jsonUser.getString("username"));
        // some fields are not set for private profiles
        user.setEmail(jsonUser.has("email") ? jsonUser.getString("email") : "");
        user.setFirstName(jsonUser.has("first_name") ? jsonUser.getString("first_name") : "");
        user.setLastName(jsonUser.has("last_name") ? jsonUser.getString("last_name") : "");

        users.add(user);
      }

      return PagedResult.create(users, totalResults);
    } catch (JSONException e) {
      throw new ReviewboardException(e.getMessage(), e);
    }
  }
  public PagedResult<ReviewRequest> readReviewRequests(String source) throws ReviewboardException {
    try {

      JSONObject object = checkedGetJSonRootObject(source);
      int totalResult = object.getInt("total_results");

      JSONArray jsonReviewRequests = object.getJSONArray("review_requests");
      List<ReviewRequest> reviewRequests = new ArrayList<ReviewRequest>();

      for (int i = 0; i < jsonReviewRequests.length(); i++)
        reviewRequests.add(readReviewRequest(jsonReviewRequests.getJSONObject(i)));

      return PagedResult.create(reviewRequests, totalResult);
    } catch (Exception e) {
      throw new ReviewboardException(e.getMessage(), e);
    }
  }
  public PagedResult<Change> readChanges(String source) throws ReviewboardException {

    try {
      JSONObject json = checkedGetJSonRootObject(source);

      int totalResults = json.getInt("total_results");
      JSONArray jsonChanges = json.getJSONArray("changes");

      List<Change> changesList = Lists.newArrayList();
      for (int i = 0; i < jsonChanges.length(); i++)
        changesList.add(readChangeObject(jsonChanges.getJSONObject(i)));

      return PagedResult.create(changesList, totalResults);

    } catch (JSONException e) {
      throw new ReviewboardException(e.getMessage(), e);
    }
  }
  public PagedResult<Screenshot> readScreenshots(String source) throws ReviewboardException {

    try {
      JSONObject json = checkedGetJSonRootObject(source);

      int totalResults = json.getInt("total_results");
      JSONArray jsonScreenshots = json.getJSONArray("screenshots");

      List<Screenshot> screenshotList = new ArrayList<Screenshot>();
      for (int i = 0; i < jsonScreenshots.length(); i++)
        screenshotList.add(readScreenshot(jsonScreenshots.getJSONObject(i)));

      return PagedResult.create(screenshotList, totalResults);

    } catch (JSONException e) {
      throw new ReviewboardException(e.getMessage(), e);
    }
  }
  public PagedResult<Review> readReviews(String source) throws ReviewboardException {

    try {
      JSONObject rootObject = checkedGetJSonRootObject(source);
      int totalResults = rootObject.getInt("total_results");
      JSONArray jsonReviews = rootObject.getJSONArray("reviews");

      List<Review> reviews = new ArrayList<Review>();
      for (int i = 0; i < jsonReviews.length(); i++) {

        JSONObject jsonReview = jsonReviews.getJSONObject(i);

        reviews.add(getReview(jsonReview));
      }

      return PagedResult.create(reviews, totalResults);
    } catch (JSONException e) {
      throw new ReviewboardException(e.getMessage(), e);
    }
  }
  public PagedResult<Diff> readDiffs(String source) throws ReviewboardException {

    try {
      JSONObject json = checkedGetJSonRootObject(source);

      int totalResults = json.getInt("total_results");
      JSONArray jsonDiffs = json.getJSONArray("diffs");

      List<Diff> diffList = new ArrayList<Diff>();
      for (int i = 0; i < jsonDiffs.length(); i++) {

        JSONObject jsonDiff = jsonDiffs.getJSONObject(i);

        diffList.add(parseDiff(jsonDiff));
      }

      return PagedResult.create(diffList, totalResults);

    } catch (JSONException e) {
      throw new ReviewboardException(e.getMessage(), e);
    }
  }