Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ List<Product> products = Arrays.asList(
);

// 3. Export to Excel
DefaultExcelExporter<Product> exporter = DefaultExcelExporter.builder(Product.class, products)
SXSSFExporter<Product> exporter = SXSSFExporter.builder(Product.class, products)
.sheetStrategy(SheetStrategy.MULTI_SHEET) // Optional, MULTI_SHEET is default
.maxRows(100) // Optional, Max row of SpreadsheetVersion.EXCEL2007 is default
.sheetName("Products") // Optional, if not specified sheets will be named Sheet0, Sheet1, etc.
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ plugins {
}

group = 'io.github.hee9841.excel'
version = '0.0.4'
version = '0.1.0'

repositories {
mavenCentral()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
*
* // Usage example:
* List{@literal <}UserData{@literal >} users = getUserData();
* DefaultExcelExporter{@literal <}UserData{@literal >} exporter = DefaultExcelExporter.builder(UserData.class, users)
* SXSSFExporter{@literal <}UserData{@literal >} exporter = SXSSFExporter.builder(UserData.class, users)
* .sheetName("Users")
* .build();
* exporter.write(new FileOutputStream("users.xlsx"));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package io.github.hee9841.excel.core.exporter;

import io.github.hee9841.excel.core.meta.ColumnInfo;
import io.github.hee9841.excel.core.meta.ColumnInfoMapper;
import io.github.hee9841.excel.exception.ExcelException;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Abstract base class for Excel file operations using Apache POI's SXSSF (Streaming XML Spreadsheet
* Format).
* This class provides the core functionality for handling Excel files with streaming support for
* large datasets.
*
* <p>Key features:</p>
* <ul>
* <li>Uses SXSSFWorkbook for memory-efficient handling of large Excel files</li>
* <li>Supports Excel 2007+ format (XLSX)</li>
* <li>Provides column mapping and header generation</li>
* <li>Handles cell styling and data type conversion</li>
* </ul>
*
* <p>This class implements the core functionality while leaving sheet management strategies
* to be implemented by concrete subclasses.</p>
*
* @param <T> The type of data to be handled in the Excel file
*/
public abstract class AbstractExcelExporter<T> implements ExcelExporter<T> {

protected static final Logger logger = LoggerFactory.getLogger(AbstractExcelExporter.class);

protected static final SpreadsheetVersion supplyExcelVersion = SpreadsheetVersion.EXCEL2007;

protected SXSSFWorkbook workbook;
protected Map<Integer, ColumnInfo> columnsMappingInfo;

protected String dtoTypeName;

/**
* Constructs a new AbstractExcelExporter with a new SXSSFWorkbook instance.
*/
protected AbstractExcelExporter() {
this.workbook = new SXSSFWorkbook();
}

/**
* Initializes the Excel file with the specified type and data.
* This method performs validation and sets up column mapping information.
*
* @param type The class type of the data to be exported
* @param data The list of data objects to be exported
*/
protected void initialize(Class<?> type, List<T> data) {
this.dtoTypeName = type.getName();
logger.info("Initializing Excel file for DTO: {}.java.", dtoTypeName);

validate(type, data);

logger.debug("Mapping DTO to Excel data - DTO class({}).", dtoTypeName);
//Map DTO to Excel data
this.columnsMappingInfo = ColumnInfoMapper.of(type, workbook).map();
}


/**
* Creates headers using the column mapping information.
*
* @param sheet The sheet to add headers to
* @param headerRowIndex The headers row index
*/
protected void createHeader(Sheet sheet, Integer headerRowIndex) {
Row row = sheet.createRow(headerRowIndex);
for (Integer colIndex : columnsMappingInfo.keySet()) {
ColumnInfo columnMappingInfo = columnsMappingInfo.get(colIndex);
Cell cell = row.createCell(colIndex);
cell.setCellValue(columnMappingInfo.getHeaderName());
cell.setCellStyle(columnMappingInfo.getHeaderStyle());
}
}

/**
* Creates a new sheet with headers.
*
* <p>This method resets the current row index, creates a new sheet, and adds headers to it.
* If a sheet name is provided, it will be used as a base name with an index(index starts from
* 0) suffix.</p>
*/
protected Sheet createNewSheet(String sheetName, int sheetIndex) {
//If sheet name is provided, create sheet with sheet name + idx
final String finalSheetName = (sheetName != null)
? String.format("%s(%d)", sheetName, sheetIndex)
: null;


Sheet sheet = (finalSheetName != null)
? workbook.createSheet(finalSheetName)
: workbook.createSheet();

logger.debug("Create new Sheet : {}.", sheet.getSheetName());

return sheet;
}

/**
* Creates a row in the Excel sheet for the given data object.
* This method handles field access and cell value setting based on column mapping information.
*
* @param sheet The Sheet object to create a row.
* @param data The data object for rendering data to cell
* @param rowIndex The index of the row to create
* @throws ExcelException if field access fails
*/
protected void createBody(Sheet sheet, Object data, int rowIndex) {
logger.debug("Add rows data - row:{}.", rowIndex);
Row row = sheet.createRow(rowIndex);
for (Integer colIndex : columnsMappingInfo.keySet()) {
ColumnInfo columnInfo = columnsMappingInfo.get(colIndex);
try {
Field field = FieldUtils.getField(data.getClass(), columnInfo.getFieldName(), true);
Cell cell = row.createCell(colIndex);
//Set cell value by cell type
columnInfo.getColumnType().setCellValueByCellType(cell, field.get(data));
//Set cell style
cell.setCellStyle(columnInfo.getBodyStyle());
} catch (IllegalAccessException e) {
throw new ExcelException(
String.format("Failed to create body(column:%d, row:%d) : "
+ "Access to field %s failed.",
colIndex, rowIndex, columnInfo.getFieldName()), e);
}
}
}

/**
* Writes the Excel file content to the specified output stream.
* This method ensures proper resource cleanup using try-with-resources.
*
* @param stream The output stream to write the Excel file to
* @throws IOException if an I/O error occurs during writing
*/
@Override
public final void write(OutputStream stream) throws IOException {
if (stream == null) {
throw new ExcelException("Output stream is null.");
}
logger.info("Start to write Excel file for DTO class({}.java).", dtoTypeName);

try (SXSSFWorkbook autoCloseableWb = this.workbook) {
autoCloseableWb.write(stream);
logger.info("Successfully wrote Excel file for DTO class({}.java).", dtoTypeName);
}
}

/**
* Validates the provided data and type.
* This method can be overridden by subclasses to add custom validation logic.
*
* @param type The class of the data type
* @param data The list of data objects to be exported
*/
protected abstract void validate(Class<?> type, List<T> data);

/**
* Creates the Excel file with the provided data.
* This method must be implemented by subclasses to define their specific sheet management
* strategy.
*
* @param data The list of data objects to be exported
*/
protected abstract void createExcel(List<T> data);

}
Loading