private void processWithoutGrouping(List<TaskWithWorklogs> tasks, DisplayData displayData) {
    LOGGER.debug("Processing without grouping");
    tasks
        .stream()
        .sorted((o1, o2) -> COLLATOR.compare(o1.getIssue(), o2.getIssue()))
        .forEach(
            taskWithWorklogs -> {
              DisplayRow row = new DisplayRow();
              row.setIssueId(taskWithWorklogs.getIssue());
              row.setLabel(taskWithWorklogs.getSummary());
              row.setResolvedDate(taskWithWorklogs.getResolved());

              taskWithWorklogs
                  .getWorklogItemList()
                  .forEach(
                      worklogItem -> {
                        LocalDate date = worklogItem.getDate();
                        DisplayDayEntry workdayEntry =
                            row.getWorkdayEntry(date)
                                .orElseGet(
                                    () -> {
                                      DisplayDayEntry newWorkdayEntry = new DisplayDayEntry();
                                      newWorkdayEntry.setDate(date);
                                      row.addDisplayDayEntry(newWorkdayEntry);
                                      return newWorkdayEntry;
                                    });

                        workdayEntry.getSpentTime().addAndGet(worklogItem.getDurationInMinutes());
                      });

              displayData.addRow(new TreeItem<>(row));
            });
  }
  private void processWithGrouping(List<TaskWithWorklogs> tasks, DisplayData displayData) {
    LOGGER.debug("Processing with grouping");
    List<String> distinctGroupByCriteria =
        tasks
            .stream()
            .map(TaskWithWorklogs::getDistinctGroupByCriteriaValues)
            .flatMap(Collection::stream)
            .distinct()
            .sorted(COLLATOR)
            .collect(Collectors.toList());

    distinctGroupByCriteria.forEach(
        groupByCriteria -> {
          LOGGER.debug("Gathering data for group criteria value {}", groupByCriteria);
          DisplayRow groupCaptionRow = new DisplayRow();
          groupCaptionRow.setIsGroupContainer(true);
          groupCaptionRow.setLabel(groupByCriteria);

          TreeItem<DisplayRow> groupRow = new TreeItem<>(groupCaptionRow);
          groupRow.setExpanded(true);
          Map<String, DisplayRow> ticketIdToDisplayRow = Maps.newHashMap();

          // add sub rows to groupRow
          tasks
              .stream()
              .filter(
                  taskWithWorklogs ->
                      taskWithWorklogs.getDistinctGroupByCriteriaValues().contains(groupByCriteria))
              .sorted((o1, o2) -> COLLATOR.compare(o1.getIssue(), o2.getIssue()))
              .forEach(
                  taskWithWorklogs -> {
                    // this task with worklogs contains at least one workitem
                    // having the group by criteria

                    DisplayRow ticketRowWithinThisGroup =
                        ticketIdToDisplayRow.get(taskWithWorklogs.getIssue());
                    if (ticketRowWithinThisGroup == null) {
                      ticketRowWithinThisGroup = new DisplayRow();
                      ticketRowWithinThisGroup.setLabel(taskWithWorklogs.getSummary());
                      ticketRowWithinThisGroup.setIssueId(taskWithWorklogs.getIssue());
                      ticketRowWithinThisGroup.setResolvedDate(taskWithWorklogs.getResolved());
                      groupRow.getChildren().add(new TreeItem<>(ticketRowWithinThisGroup));
                      ticketIdToDisplayRow.put(
                          taskWithWorklogs.getIssue(), ticketRowWithinThisGroup);
                    }

                    DisplayRow ticketRowWithinThisGroupAsFinal = ticketRowWithinThisGroup;

                    taskWithWorklogs
                        .getWorklogItemList()
                        .stream()
                        .filter(
                            worklogItem ->
                                StringUtils.equals(worklogItem.getGroup(), groupByCriteria))
                        .sorted((o1, o2) -> o1.getDate().compareTo(o2.getDate()))
                        .forEach(
                            worklogItem -> {
                              // this worklog item matches the critera
                              // add workday entry to current row
                              LocalDate date = worklogItem.getDate();

                              DisplayDayEntry workdayEntry =
                                  ticketRowWithinThisGroupAsFinal
                                      .getWorkdayEntry(date)
                                      .orElseGet(
                                          () -> {
                                            DisplayDayEntry displayDayEntry = new DisplayDayEntry();
                                            displayDayEntry.setDate(date);
                                            ticketRowWithinThisGroupAsFinal.addDisplayDayEntry(
                                                displayDayEntry);

                                            return displayDayEntry;
                                          });

                              workdayEntry
                                  .getSpentTime()
                                  .addAndGet(worklogItem.getDurationInMinutes());

                              // also add up the spent time in the group header per group
                              workdayEntry =
                                  groupCaptionRow
                                      .getWorkdayEntry(date)
                                      .orElseGet(
                                          () -> {
                                            DisplayDayEntry newWorkdayEntry = new DisplayDayEntry();
                                            newWorkdayEntry.setDate(date);
                                            groupCaptionRow.addDisplayDayEntry(newWorkdayEntry);
                                            return newWorkdayEntry;
                                          });
                              workdayEntry
                                  .getSpentTime()
                                  .addAndGet(worklogItem.getDurationInMinutes());
                            });
                  });

          // add groupRow to result
          displayData.addRow(groupRow);
        });
  }
  private DisplayData getDisplayData(
      FetchTimereportContext reportContext, boolean changedSinceLastRender) {

    if (changedSinceLastRender) {
      LOGGER.debug("Refreshing display data");
      DisplayData displayData = new DisplayData();

      WorklogReport result = reportContext.getResult().get();
      ImmutableList<TaskWithWorklogs> originalTasks = result.getTasks();

      // create a copy of the original task list
      // and pass it on to the getFilteredList method
      // which then may freely modify the list and its items
      List<TaskWithWorklogs> deepCopiedList = Lists.newArrayList();
      originalTasks.forEach(
          taskWithWorklogs -> deepCopiedList.add(taskWithWorklogs.createDeepCopy()));
      List<TaskWithWorklogs> filteredList = getFilteredList(deepCopiedList);

      // render the treetabledata
      if (reportContext.getGroupByCategory().isPresent()) {
        // grouping present
        processWithGrouping(filteredList, displayData);
      } else {
        // no grouping
        processWithoutGrouping(filteredList, displayData);
      }

      // add grandtotal column
      DisplayRow grandTotal = new DisplayRow();
      grandTotal.setIsGrandTotalSummary(true);
      displayData.addRow(new TreeItem<>(grandTotal));

      filteredList
          .stream()
          .map(TaskWithWorklogs::getWorklogItemList)
          .flatMap(Collection::stream)
          .forEach(
              worklogItem -> {
                LocalDate date = worklogItem.getDate();

                DisplayDayEntry workdayEntry =
                    grandTotal
                        .getWorkdayEntry(date)
                        .orElseGet(
                            () -> {
                              DisplayDayEntry displayDayEntry = new DisplayDayEntry();
                              displayDayEntry.setDate(date);

                              grandTotal.addDisplayDayEntry(displayDayEntry);

                              return displayDayEntry;
                            });

                workdayEntry.getSpentTime().addAndGet(worklogItem.getDurationInMinutes());
              });

      // call statistics update
      updateStatisticsData(filteredList);

      resultItemsToDisplay = Optional.of(displayData);
    }

    return resultItemsToDisplay.get();
  }