public record ExcelFileWriter()

in extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/ExcelFileWriter.java [52:204]


public record ExcelFileWriter(@Nullable Options options) {

    @Builder
    public record Options(
            /**
             * Custom style for individual cell based on cell value. Overrules rowStyleFunction if provided.
             * <p>
             * {@link Function} may return {@code null}.
             */
            @Nullable Function<TabularCell, CustomCellStyle> cellStyleFunction,
            /**
             * Custom style for entire row based on row data. Overruled by cellStyleFunction if provided.
             * {@link Function} may return {@code null}.<br>
             */
            @Nullable Function<TabularRow, CustomCellStyle> rowStyleFunction) {

        public enum CustomCellStyle {
            DEFAULT,
            BLUE,
            GREEN,
            INDIGO,
            WARNING,
            DANGER;
            public boolean isDefault() { return this == CustomCellStyle.DEFAULT; }
            static CustomCellStyle nullToDefault(final @Nullable CustomCellStyle customCellStyle) {
                return customCellStyle!=null
                        ? customCellStyle
                        : DEFAULT;
            }
        }

    }

    @SneakyThrows
    public void write(final TabularModel tabular, final File tempFile) {
        try(final Workbook wb = new SXSSFWorkbook()) {
            tabular.sheets().forEach(sheet->writeSheet(wb, sheet));
            try(var fos = new FileOutputStream(tempFile)) {
                wb.write(fos);
            }
        }
    }

    /**
     * Writes given tabular data to a {@link Blob}, using given name as blob name.
     */
    @SneakyThrows
    public Blob writeToBlob(final String name, final TabularModel tabular) {
        var tempFile = File.createTempFile(this.getClass().getCanonicalName(), name);
        try {
            write(tabular, tempFile);
            return Blob.of(name, CommonMimeType.XLSX, DataSource.ofFile(tempFile).bytes());
        } finally {
            Files.deleteIfExists(tempFile.toPath()); // cleanup
        }
    }

    // -- HELPER

    @RequiredArgsConstructor
    private static class RowFactory {
        private final Sheet sheet;
        private int rowNum;
        public Row newRow() {
            return sheet.createRow(rowNum++);
        }
    }

    private void writeSheet(final Workbook wb, final TabularModel.TabularSheet tabularSheet) {

        var sheetName = tabularSheet.sheetName();

        Row row;
        
        var sheet = wb.createSheet(sheetName);
        if(sheet instanceof SXSSFSheet sxssfSheet) {
            sxssfSheet.trackAllColumnsForAutoSizing();
        }
        var cellWriter = new ExcelCellWriter(5, new ExcelImageHandler(sheet));
        
        var cellStyleProvider = CellStyleProvider.create(wb, options);
        var rowFactory = new RowFactory(sheet);

        var dataColumns = tabularSheet.columns();

        // primary header row
        row = rowFactory.newRow();
        int i=0;
        for(var column : dataColumns) {
            final Cell cell = row.createCell((short) i++);
            cell.setCellValue(column.columnName());
            cell.setCellStyle(cellStyleProvider.primaryHeaderStyle());
        }

        // secondary header row
        row = rowFactory.newRow();
        i=0;
        var maxLinesInRow = _Reduction.of(1, Math::max); // row auto-size calculation
        for(var column : dataColumns) {
            final Cell cell = row.createCell((short) i++);
            final String columnDescription = column.columnDescription();
            cell.setCellValue(columnDescription);
            maxLinesInRow.accept((int)
                _Strings.splitThenStream(columnDescription, "\n").count());
            cell.setCellStyle(cellStyleProvider.secondaryHeaderStyle());
        }
        autoSizeRow(row, maxLinesInRow.getResult().orElse(1),
            wb.getFontAt(cellStyleProvider.secondaryHeaderStyle().getFontIndex()));

        var dataRows = tabularSheet.rows();

        // detail rows
        for (var dataRow : dataRows) {
            row = rowFactory.newRow();
            i=0;
            maxLinesInRow = _Reduction.of(1, Math::max); // row auto-size calculation
            for(var column : dataColumns) {
                final Cell cell = row.createCell((short) i++);
                final TabularCell tabularCell = dataRow.getCell(column);
                final int linesWritten = cellWriter.setCellValue(
                    column,
                    tabularCell,
                    cell,
                    cellStyleProvider);
                maxLinesInRow.accept(linesWritten);
            }
            cellStyleProvider.applyCustomStyle(dataRow, row);
            autoSizeRow(row, maxLinesInRow.getResult().orElse(1), null);
        }

        // column auto-size
        autoSizeColumns(sheet, dataColumns.size());

        // freeze panes
        sheet.createFreezePane(0, 2);
    }

    private void autoSizeRow(final Row row, final int numberOfLines, final @Nullable Font fontHint) {
        if(numberOfLines<2) return; // ignore
        final int defaultHeight = fontHint!=null
                ? fontHint.getFontHeight()
                : row.getSheet().getDefaultRowHeight();
        int height = numberOfLines * defaultHeight;
        height = Math.min(height, Short.MAX_VALUE); // upper bound to 32767 'twips' or 1/20th of a point
        row.setHeight((short) height);
    }

    private void autoSizeColumns(final Sheet sheet, final int columnCount) {
        IntStream.range(0, columnCount)
            .forEach(sheet::autoSizeColumn);
    }

}