/**
   * Merges a collection of map data sets
   *
   * @param dataSets the data sets
   * @return the merged data set
   */
  protected MapDataSet mergeDataSets(
      List<MapDataSet> dataSets,
      MergingDataSetDefinition dataSetDefinition,
      EvaluationContext context) {
    MapDataSet ret = new MapDataSet(dataSetDefinition, context);

    List<DataSetColumn> columns = new ArrayList<DataSetColumn>();

    // Gather all columns from all contained data sets
    for (DataSet dataSet : dataSets) {
      for (DataSetColumn column : dataSet.getMetaData().getColumns()) {
        columns.add(column);
      }
    }

    // Sort the columns according to the merge order
    if (MergingDataSetDefinition.MergeOrder.NAME.equals(dataSetDefinition.getMergeOrder())) {
      Collections.sort(
          columns,
          new Comparator<DataSetColumn>() {
            @Override
            public int compare(DataSetColumn column1, DataSetColumn column2) {
              return OpenmrsUtil.compareWithNullAsGreatest(column1.getName(), column2.getName());
            }
          });
    } else if (MergingDataSetDefinition.MergeOrder.LABEL.equals(
        dataSetDefinition.getMergeOrder())) {
      Collections.sort(
          columns,
          new Comparator<DataSetColumn>() {
            @Override
            public int compare(DataSetColumn column1, DataSetColumn column2) {
              return OpenmrsUtil.compareWithNullAsGreatest(column1.getLabel(), column2.getLabel());
            }
          });
    }

    ret.getMetaData().setColumns(columns);

    // Gather column data values from all contained data sets
    for (MapDataSet dataSet : dataSets) {
      for (DataSetColumn column : dataSet.getMetaData().getColumns()) {
        ret.addData(column, getDataSetData(dataSet, column));
      }
    }

    return ret;
  }
  /** @see ReportRenderer#render(ReportData, String, OutputStream) */
  public void render(ReportData reportData, String argument, OutputStream out)
      throws IOException, RenderingException {

    try {
      log.debug("Attempting to render report with ExcelTemplateRenderer");
      ReportDesign design = getDesign(argument);
      Workbook wb = getExcelTemplate(design);

      if (wb == null) {
        XlsReportRenderer xlsRenderer = new XlsReportRenderer();
        xlsRenderer.render(reportData, argument, out);
      } else {
        Map<String, String> repeatSections = getRepeatingSections(design);

        // Put together base set of replacements.  Any dataSet with only one row is included.
        Map<String, Object> replacements = getBaseReplacementData(reportData, design);

        // Iterate across all of the sheets in the workbook, and configure all those that need to be
        // added/cloned
        List<SheetToAdd> sheetsToAdd = new ArrayList<SheetToAdd>();

        Set<String> usedSheetNames = new HashSet<String>();
        int numberOfSheets = wb.getNumberOfSheets();

        for (int sheetNum = 0; sheetNum < numberOfSheets; sheetNum++) {

          Sheet currentSheet = wb.getSheetAt(sheetNum);
          String originalSheetName = wb.getSheetName(sheetNum);

          String dataSetName = getRepeatingSheetProperty(sheetNum, repeatSections);
          if (dataSetName != null) {

            DataSet repeatingSheetDataSet = getDataSet(reportData, dataSetName, replacements);
            int dataSetRowNum = 0;
            for (Iterator<DataSetRow> rowIterator = repeatingSheetDataSet.iterator();
                rowIterator.hasNext(); ) {
              DataSetRow dataSetRow = rowIterator.next();
              dataSetRowNum++;
              Map<String, Object> newReplacements =
                  getReplacementData(
                      replacements, reportData, design, dataSetName, dataSetRow, dataSetRowNum);
              Sheet newSheet = (dataSetRowNum == 1 ? currentSheet : wb.cloneSheet(sheetNum));
              sheetsToAdd.add(
                  new SheetToAdd(newSheet, sheetNum, originalSheetName, newReplacements));
            }
          } else {
            sheetsToAdd.add(
                new SheetToAdd(currentSheet, sheetNum, originalSheetName, replacements));
          }
        }

        // Then iterate across all of these and add them in
        for (int i = 0; i < sheetsToAdd.size(); i++) {
          addSheet(wb, sheetsToAdd.get(i), usedSheetNames, reportData, design, repeatSections);
        }

        wb.write(out);
      }
    } catch (Exception e) {
      throw new RenderingException("Unable to render results due to: " + e, e);
    }
  }