This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch v3
in repository https://gitbox.apache.org/repos/asf/causeway.git


The following commit(s) were added to refs/heads/v3 by this push:
     new 2a8dc8f9a2b CAUSEWAY-3825: excel exporter: allow for individual cell 
coloring (v3 only)
2a8dc8f9a2b is described below

commit 2a8dc8f9a2b86b2c689fadef94d3f33873de716b
Author: Andi Huber <[email protected]>
AuthorDate: Thu Nov 7 11:53:32 2024 +0100

    CAUSEWAY-3825: excel exporter: allow for individual cell coloring (v3
    only)
---
 .../tabular/excel/exporter/CellStyleProvider.java  | 195 ++++++++++++++++++---
 .../tabular/excel/exporter/ExcelCellWriter.java    |   4 +-
 .../tabular/excel/exporter/ExcelFileWriter.java    |  43 ++++-
 .../excel/exporter/TabularExcelExporter.java       |  15 +-
 4 files changed, 227 insertions(+), 30 deletions(-)

diff --git 
a/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/CellStyleProvider.java
 
b/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/CellStyleProvider.java
index b564bc2516a..9a02c60c40b 100644
--- 
a/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/CellStyleProvider.java
+++ 
b/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/CellStyleProvider.java
@@ -21,6 +21,7 @@ package org.apache.causeway.extensions.tabular.excel.exporter;
 import java.awt.Color;
 
 import org.apache.poi.ss.usermodel.BorderStyle;
+import org.apache.poi.ss.usermodel.Cell;
 import org.apache.poi.ss.usermodel.CellStyle;
 import org.apache.poi.ss.usermodel.FillPatternType;
 import org.apache.poi.ss.usermodel.VerticalAlignment;
@@ -28,36 +29,162 @@ import org.apache.poi.ss.usermodel.Workbook;
 import org.apache.poi.xssf.usermodel.DefaultIndexedColorMap;
 import org.apache.poi.xssf.usermodel.XSSFColor;
 
-import lombok.Getter;
+import org.springframework.lang.Nullable;
 
-import lombok.experimental.Accessors;
+import org.apache.causeway.commons.tabular.TabularModel.TabularRow;
+import 
org.apache.causeway.extensions.tabular.excel.exporter.ExcelFileWriter.Options.CustomCellStyle;
 
-//record candidate
-@Getter @Accessors(fluent=true)
-class CellStyleProvider {
+import lombok.NonNull;
 
-    public CellStyleProvider(final Workbook wb) {
-        this.workbook = wb;
-        this.primaryHeaderStyle = createPrimaryHeaderRowStyle(wb);
-        this.secondaryHeaderStyle = createSecondaryHeaderRowStyle(wb);
-        this.dateStyle = createDateFormatCellStyle(wb, "yyyy-mm-dd");
-        this.multilineStyle = createMultilineCellStyle(wb);
+record CellStyleProvider(
+        @NonNull Workbook workbook,
+        @NonNull CellStyle primaryHeaderStyle,
+        @NonNull CellStyle secondaryHeaderStyle,
+        @NonNull CellStyle dateStyle,
+        @NonNull CellStyle multilineStyle,
+        @NonNull CellStyle[] blueStyles,
+        @NonNull CellStyle[] greenStyles,
+        @NonNull CellStyle[] indigoStyles,
+        @NonNull CellStyle[] warningStyles,
+        @NonNull CellStyle[] dangerStyles,
+        @NonNull ExcelFileWriter.Options options) {
+
+    static CellStyleProvider create(final Workbook wb, @Nullable final 
ExcelFileWriter.Options options) {
+        var styleIndexList = new StyleIndexList();
+        return new CellStyleProvider(wb,
+            createPrimaryHeaderRowStyle(wb),
+            createSecondaryHeaderRowStyle(wb),
+            createDateFormatCellStyle(wb, styleIndexList, "yyyy-mm-dd"),
+            createMultilineCellStyle(wb, styleIndexList),
+            createColoredStyles(wb, styleIndexList, new Color(0x6ea8fe)), // 
blue-300 (6ea8fe)
+            createColoredStyles(wb, styleIndexList, new Color(0x75b798)), // 
green-300 (75b798)
+            createColoredStyles(wb, styleIndexList, new Color(0xa370f7)), // 
indigo-300 (a370f7)
+            createColoredStyles(wb, styleIndexList, new Color(0xffcd39)), // 
warning yellow-400 (ffcd39)
+            createColoredStyles(wb, styleIndexList, new Color(0xe35d6a)), // 
danger red-400 (e35d6a)
+            options!=null
+                ? options
+                : ExcelFileWriter.Options.builder().build());
+    }
+
+    /**
+     * Applies a custom cell style to given {@link Cell}
+     * based on whether any of the cellStyleFunctions is available
+     * and if so, whether any of the cellStyleFunctions returns a non-null 
CellStyle.
+     * @implNote we are doing this distinction of cases for performance 
reasons,
+     *      that is, minimize the number of style function calls;
+     *      otherwise code could be simplified
+     */
+    void applyCustomStyle(final TabularRow tabularRow, final Iterable<Cell> 
cells) {
+        switch (StylePolicy.get(options)) {
+            case NONE: return;
+            case ROW_ONLY: {
+                var customRowStyle = CustomCellStyle.nullToDefault(
+                        options.rowStyleFunction().apply(tabularRow));
+                if(customRowStyle.isDefault()) return;
+                for(var cell : cells) {
+                    applyCustomCellStyle(cell, customRowStyle);
+                }
+                return;
+            }
+            case CELL_ONLY: {
+                int cellIndex = 0;
+                for(var cell : cells) {
+                    var customCellStyle = CustomCellStyle.nullToDefault(
+                            
options.cellStyleFunction().apply(tabularRow.getCell(cellIndex++)));
+                    applyCustomCellStyle(cell, customCellStyle);
+                }
+                return;
+            }
+            case CELL_AND_ROW: {
+                CustomCellStyle customRowStyle = null; // only calculate if 
needed
+                int cellIndex = 0;
+                for(var cell : cells) {
+                    var customCellStyle = CustomCellStyle.nullToDefault(
+                            
options.cellStyleFunction().apply(tabularRow.getCell(cellIndex++)));
+                    if(!customCellStyle.isDefault()) {
+                        applyCustomCellStyle(cell, customCellStyle);
+                    } else {
+                        if(customRowStyle==null) {
+                            customRowStyle = CustomCellStyle.nullToDefault(
+                                    
options.rowStyleFunction().apply(tabularRow));
+                        }
+                        applyCustomCellStyle(cell, customRowStyle);
+                    }
+                }
+                return;
+            }
+        }
+    }
+
+    Cell applyDateStyle(final Cell cell) {
+        cell.setCellStyle(dateStyle());
+        return cell;
+    }
+
+    Cell applyMultilineStyle(final Cell cell) {
+        cell.setCellStyle(multilineStyle());
+        return cell;
+    }
+
+    private void applyCustomCellStyle(final Cell cell, final CustomCellStyle 
customCellStyle) {
+        if(customCellStyle==null
+                || customCellStyle.isDefault()) return;
+
+        final int ordinal = valueKindOf(cell).ordinal();
+
+        cell.setCellStyle(
+            switch (customCellStyle) {
+                case BLUE -> blueStyles()[ordinal];
+                case GREEN -> greenStyles()[ordinal];
+                case INDIGO -> indigoStyles()[ordinal];
+                case WARNING -> warningStyles()[ordinal];
+                case DANGER -> dangerStyles()[ordinal];
+                case DEFAULT -> throw new 
UnsupportedOperationException("unexpected code reach");
+            });
     }
 
-    final Workbook workbook;
-    final CellStyle primaryHeaderStyle;
-    final CellStyle secondaryHeaderStyle;
-    final CellStyle dateStyle;
-    final CellStyle multilineStyle;
+    private enum ValueKind {
+        DEFAULT,
+        DATE,
+        MULTILINE;
+    }
+    private ValueKind valueKindOf(final Cell cell) {
+        if(cell.getCellStyle() == dateStyle()) return ValueKind.DATE;
+        if(cell.getCellStyle() == multilineStyle()) return ValueKind.MULTILINE;
+        return ValueKind.DEFAULT;
+    }
+
+    /**
+     * Creates a colored variant for each {@link ValueKind}.
+     * @param styleIndexList
+     */
+    private static CellStyle[] createColoredStyles(final Workbook wb, final 
StyleIndexList styleIndexList, final Color color) {
+        var valueKinds = ValueKind.values();
+        var cellStyles = new CellStyle[valueKinds.length];
+        for(var valueKind : valueKinds) {
 
-    protected CellStyle createPrimaryHeaderRowStyle(final Workbook wb) {
+            var cellStyle = wb.createCellStyle();
+
+            switch (valueKind) {
+                case DEFAULT -> {}
+                case DATE -> 
cellStyle.cloneStyleFrom(wb.getCellStyleAt(styleIndexList.dateStyleIndex));
+                case MULTILINE -> 
cellStyle.cloneStyleFrom(wb.getCellStyleAt(styleIndexList.multilineStyleIndex));
+            }
+
+            cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+            cellStyle.setFillForegroundColor(createColor(color));
+            cellStyles[valueKind.ordinal()] = cellStyle;
+        }
+        return cellStyles;
+    }
+
+    private static CellStyle createPrimaryHeaderRowStyle(final Workbook wb) {
         var font = wb.createFont();
         font.setBold(true);
         var cellStyle = wb.createCellStyle();
         cellStyle.setFont(font);
         cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
         cellStyle.setFillForegroundColor(createColor(new Color(0xc0c0c0)));
-        //cellStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());
         cellStyle.setBorderLeft(BorderStyle.THIN);
         cellStyle.setBorderRight(BorderStyle.THIN);
         cellStyle.setBorderTop(BorderStyle.THIN);
@@ -65,7 +192,7 @@ class CellStyleProvider {
         return cellStyle;
     }
 
-    protected CellStyle createSecondaryHeaderRowStyle(final Workbook wb) {
+    private static CellStyle createSecondaryHeaderRowStyle(final Workbook wb) {
         var font = wb.createFont();
         font.setFontHeightInPoints((short) 10);
         var cellStyle = wb.createCellStyle();
@@ -78,25 +205,47 @@ class CellStyleProvider {
         cellStyle.setBorderRight(BorderStyle.THIN);
         cellStyle.setBorderTop(BorderStyle.NONE);
         cellStyle.setBorderBottom(BorderStyle.MEDIUM);
-        //cellStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());
         return cellStyle;
     }
 
-    protected CellStyle createDateFormatCellStyle(final Workbook wb, final 
String dateFormat) {
+    private static CellStyle createDateFormatCellStyle(final Workbook wb, 
final StyleIndexList styleIndexList, final String dateFormat) {
         var cellStyle = wb.createCellStyle();
         
cellStyle.setDataFormat(wb.getCreationHelper().createDataFormat().getFormat(dateFormat));
+        styleIndexList.dateStyleIndex = cellStyle.getIndex();
         return cellStyle;
     }
 
-    protected CellStyle createMultilineCellStyle(final Workbook wb) {
+    private static CellStyle createMultilineCellStyle(final Workbook wb, final 
StyleIndexList styleIndexList) {
         var cellStyle = wb.createCellStyle();
         cellStyle.setVerticalAlignment(VerticalAlignment.TOP);
         cellStyle.setWrapText(true);
+        styleIndexList.multilineStyleIndex = cellStyle.getIndex();
         return cellStyle;
     }
 
-    static XSSFColor createColor(final Color color) {
+    private static XSSFColor createColor(final Color color) {
         return new XSSFColor(color, new DefaultIndexedColorMap());
     }
 
+    private static class StyleIndexList {
+        short dateStyleIndex = -1;
+        short multilineStyleIndex = -1;
+    }
+
+    private enum StylePolicy {
+        NONE,
+        CELL_ONLY,
+        ROW_ONLY,
+        CELL_AND_ROW;
+        static StylePolicy get(final ExcelFileWriter.Options options) {
+            return options.rowStyleFunction()!=null
+                    ? options.cellStyleFunction()!=null
+                        ? StylePolicy.CELL_AND_ROW
+                        : StylePolicy.ROW_ONLY
+                    : options.cellStyleFunction()!=null
+                        ? StylePolicy.CELL_ONLY
+                        : StylePolicy.NONE;
+        }
+    }
+
 }
diff --git 
a/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/ExcelCellWriter.java
 
b/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/ExcelCellWriter.java
index bc69100571d..1d01a31ab08 100644
--- 
a/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/ExcelCellWriter.java
+++ 
b/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/ExcelCellWriter.java
@@ -64,7 +64,7 @@ record ExcelCellWriter(
                 joinedElementsLiteral += POI_LINE_DELIMITER + 
String.format("(has %d more)", overflow);
             }
             cell.setCellValue(joinedElementsLiteral);
-            cell.setCellStyle(cellStyleProvider.multilineStyle());
+            cellStyleProvider.applyMultilineStyle(cell);
             return overflow>0
                     ? maxCellElements + 1
                     : tabularCell.cardinality();
@@ -171,7 +171,7 @@ record ExcelCellWriter(
 
     private static void setCellValueForDate(final Cell cell, final Date date, 
final CellStyleProvider cellStyleProvider) {
         cell.setCellValue(date);
-        cell.setCellStyle(cellStyleProvider.dateStyle());
+        cellStyleProvider.applyDateStyle(cell);
     }
 
 }
diff --git 
a/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/ExcelFileWriter.java
 
b/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/ExcelFileWriter.java
index 1adde403b07..d275f5a910c 100644
--- 
a/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/ExcelFileWriter.java
+++ 
b/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/ExcelFileWriter.java
@@ -21,6 +21,7 @@ package org.apache.causeway.extensions.tabular.excel.exporter;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.nio.file.Files;
+import java.util.function.Function;
 import java.util.stream.IntStream;
 
 import org.apache.poi.ss.usermodel.Cell;
@@ -38,14 +39,48 @@ import org.apache.causeway.commons.internal.base._Reduction;
 import org.apache.causeway.commons.internal.base._Strings;
 import org.apache.causeway.commons.io.DataSource;
 import org.apache.causeway.commons.tabular.TabularModel;
+import org.apache.causeway.commons.tabular.TabularModel.TabularCell;
+import org.apache.causeway.commons.tabular.TabularModel.TabularRow;
 
+import lombok.Builder;
 import lombok.RequiredArgsConstructor;
 import lombok.SneakyThrows;
 
 /**
  * Utility to write a {@link TabularModel} to file.
  */
-public record ExcelFileWriter() {
+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(@Nullable final 
CustomCellStyle customCellStyle) {
+                return customCellStyle!=null
+                        ? customCellStyle
+                        : DEFAULT;
+            }
+        }
+
+    }
 
     @SneakyThrows
     public void write(final TabularModel tabular, final File tempFile) {
@@ -90,7 +125,7 @@ public record ExcelFileWriter() {
 
             var cellWriter = new ExcelCellWriter(5);
             var sheet = wb.createSheet(sheetName);
-            var cellStyleProvider = new CellStyleProvider(wb);
+            var cellStyleProvider = CellStyleProvider.create(wb, options);
             var rowFactory = new RowFactory(sheet);
 
             var dataColumns = tabularSheet.columns();
@@ -128,13 +163,15 @@ public record ExcelFileWriter() {
                 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,
-                            dataRow.getCell(column),
+                            tabularCell,
                             cell,
                             cellStyleProvider);
                     maxLinesInRow.accept(linesWritten);
                 }
+                cellStyleProvider.applyCustomStyle(dataRow, row);
                 autoSizeRow(row, maxLinesInRow.getResult().orElse(1), null);
             }
 
diff --git 
a/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/TabularExcelExporter.java
 
b/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/TabularExcelExporter.java
index 0e38ba8f0fa..ef5819f1534 100644
--- 
a/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/TabularExcelExporter.java
+++ 
b/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/TabularExcelExporter.java
@@ -20,19 +20,30 @@ package 
org.apache.causeway.extensions.tabular.excel.exporter;
 
 import java.io.File;
 
+import org.springframework.lang.Nullable;
 import org.springframework.stereotype.Component;
 
 import org.apache.causeway.applib.tabular.TabularExporter;
 import org.apache.causeway.applib.value.NamedWithMimeType.CommonMimeType;
 import org.apache.causeway.commons.tabular.TabularModel;
+import 
org.apache.causeway.extensions.tabular.excel.exporter.ExcelFileWriter.Options;
 
 @Component
 public class TabularExcelExporter
 implements TabularExporter {
 
     @Override
-    public void export(final TabularModel.TabularSheet tabularSheet, final 
File tempFile) {
-        new ExcelFileWriter().write(new TabularModel(tabularSheet), tempFile);
+    public void export(
+            final TabularModel.TabularSheet tabularSheet,
+            final File tempFile) {
+        export(tabularSheet, tempFile, (Options)null);
+    }
+
+    public void export(
+            final TabularModel.TabularSheet tabularSheet,
+            final File tempFile,
+            @Nullable final ExcelFileWriter.Options options) {
+        new ExcelFileWriter(options).write(new TabularModel(tabularSheet), 
tempFile);
     }
 
     @Override

Reply via email to