com.creditdatamw.zerocell.handler.EntityHandler Maven / Gradle / Ivy
package com.creditdatamw.zerocell.handler;
import com.creditdatamw.zerocell.ReaderUtil;
import com.creditdatamw.zerocell.ZeroCellException;
import com.creditdatamw.zerocell.ZeroCellReader;
import com.creditdatamw.zerocell.annotation.Column;
import com.creditdatamw.zerocell.annotation.RowNumber;
import com.creditdatamw.zerocell.column.ColumnInfo;
import com.creditdatamw.zerocell.column.ColumnMapping;
import com.creditdatamw.zerocell.converter.Converter;
import com.creditdatamw.zerocell.converter.Converters;
import com.creditdatamw.zerocell.converter.NoopConverter;
import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.ss.util.CellAddress;
import org.apache.poi.xssf.usermodel.XSSFComment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.lang.reflect.Field;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
public class EntityHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(EntityHandler.class);
private static final String DEFAULT_SHEET = "uploads";
private final Class type;
private final EntityExcelSheetHandler entitySheetHandler;
private final String sheetName;
public EntityHandler(Class clazz) {
Objects.requireNonNull(clazz);
this.type = clazz;
this.sheetName = DEFAULT_SHEET;
this.entitySheetHandler = createSheetHandler(clazz, null);
}
public EntityHandler(Class clazz, String sheetName) {
Objects.requireNonNull(clazz);
this.type = clazz;
this.sheetName = sheetName;
this.entitySheetHandler = createSheetHandler(clazz, null);
}
public EntityHandler(Class clazz,ColumnMapping columnMapping) {
Objects.requireNonNull(clazz);
this.type = clazz;
this.sheetName = DEFAULT_SHEET;
this.entitySheetHandler = createSheetHandler(clazz, columnMapping);
}
public EntityHandler(Class clazz, String sheetName, ColumnMapping columnMapping) {
Objects.requireNonNull(clazz);
this.type = clazz;
this.sheetName = sheetName;
this.entitySheetHandler = createSheetHandler(clazz, columnMapping);
}
@SuppressWarnings("unchecked")
private EntityExcelSheetHandler createSheetHandler(Class clazz, ColumnMapping columnMapping) {
if (columnMapping == null) {
columnMapping = readColumnInfoViaReflection(clazz);
}
final ColumnInfo rowNumberColumn = columnMapping.getRowNumberInfo();
final List list = columnMapping.getColumns();
final ColumnInfo[] columns = new ColumnInfo[list.size()];
int index = 0;
for(ColumnInfo columnInfo: list) {
index = columnInfo.getIndex();
if (index > columns.length - 1) {
throw new ZeroCellException(
"Column index out of range. index=" + index + " columnCount=" + columns.length
+ ". Ensure there @Column annotations for all indexes from 0 to " + (columns.length - 1));
}
if (! Objects.isNull(columns[index])) {
throw new ZeroCellException("Cannot map two columns to the same index: " + index);
}
columns[index] = columnInfo;
}
return new EntityExcelSheetHandler(rowNumberColumn, columns);
}
private ColumnMapping readColumnInfoViaReflection(Class> clazz) {
Field[] fieldArray = clazz.getDeclaredFields();
ArrayList list = new ArrayList<>(fieldArray.length);
ColumnInfo rowNumberColumn = null;
for (Field field: fieldArray) {
RowNumber rowNumberAnnotation = field.getAnnotation(RowNumber.class);
if (! Objects.isNull(rowNumberAnnotation)) {
rowNumberColumn = new ColumnInfo("__id__", field.getName(), -1, null,Integer.class, NoopConverter.class);
continue;
}
Column annotation = field.getAnnotation(Column.class);
if (! Objects.isNull(annotation)) {
Class> converter = annotation.convertorClass();
list.add(new ColumnInfo(annotation.name().trim(),
field.getName(),
annotation.index(),
annotation.dataFormat(),
field.getType(),
converter));
}
}
if (list.isEmpty()) {
throw new ZeroCellException(String.format("Class %s does not have @Column annotations", clazz.getName()));
}
list.trimToSize();
return new ColumnMapping(rowNumberColumn, list);
}
/**
* Returns the extracted entities as an immutable list.
* @return an immutable list of the extracted entities
*/
public List readAsList() {
List list = Collections.unmodifiableList(this.entitySheetHandler.read(null, sheetName));
return list;
}
public void process(File file) throws ZeroCellException {
ReaderUtil.process(file, sheetName, this.entitySheetHandler);
}
private final class EntityExcelSheetHandler implements ZeroCellReader {
private final Logger LOGGER = LoggerFactory.getLogger(EntityExcelSheetHandler.class);
private final ColumnInfo rowNumberColumn;
private final ColumnInfo[] columns;
private final List entities;
private final int MAXIMUM_COL_INDEX;
private boolean isHeaderRow = false;
private int currentRow = -1;
private int currentCol = -1;
private T cur;
EntityExcelSheetHandler(ColumnInfo rowNumberColumn, ColumnInfo[] columns) {
this.rowNumberColumn = rowNumberColumn;
this.columns = columns;
this.entities = new ArrayList<>();
this.MAXIMUM_COL_INDEX = columns.length - 1;
}
@Override
public List read(File file, String sheet) {
/** We don't need to process the file here since that's
* handled in {@link ReaderUtil} which MUST be used when using this class
*/
return Collections.unmodifiableList(this.entities);
}
void clear() {
this.currentRow = -1;
this.currentCol = -1;
this.cur = null;
this.entities.clear();
}
@Override
@SuppressWarnings("unchecked")
public void startRow(int i) {
currentRow = i;
// skip the header row
if (currentRow == 0) {
isHeaderRow = true;
return;
} else {
isHeaderRow = false;
}
try {
cur = (T) type.newInstance();
// Write to the field with the @RowNumber annotation here if it exists
if (! Objects.isNull(rowNumberColumn)) {
writeColumnField(cur, String.valueOf(i), rowNumberColumn, i);
}
} catch (InstantiationException | IllegalAccessException e) {
throw new ZeroCellException("Failed to create and instance of " + type.getName(), e);
}
}
@Override
public void endRow(int i) {
if (! Objects.isNull(cur)) {
this.entities.add(cur);
cur = null;
}
}
@Override
public void cell(String cellReference, String formattedValue, XSSFComment xssfComment) {
// gracefully handle missing CellRef here in a similar way as XSSFCell does
if(cellReference == null) {
cellReference = new CellAddress(currentRow, currentCol).formatAsString();
}
int column = new CellReference(cellReference).getCol();
currentCol = column;
// We ignore additional cells here since we only care about the cells
// in the columns array i.e. the defined columns
if (column > MAXIMUM_COL_INDEX) {
LOGGER.warn("Invalid Column index found: " + column);
return;
}
ColumnInfo currentColumnInfo = columns[column];
if (isHeaderRow) {
if (! currentColumnInfo.getName().equalsIgnoreCase(formattedValue.trim())){
throw new ZeroCellException(String.format("Expected Column '%s' but found '%s'", currentColumnInfo.getName(), formattedValue));
}
}
// Prevent from trying to write to a null instance
if (Objects.isNull(cur)) return;
writeColumnField(cur, formattedValue, currentColumnInfo, currentRow);
}
/**
* Write the value read from the excel cell to a field
*
* @param object the object to write to
* @param formattedValue the value read from the current excel column/row
* @param currentColumnInfo Column metadata
* @param rowNum the row number
*/
private void writeColumnField(T object, String formattedValue, ColumnInfo currentColumnInfo, int rowNum) {
String fieldName = currentColumnInfo.getFieldName();
try {
Converter converter = (Converter) currentColumnInfo.getConverterClass().newInstance();
Object value = null;
// Don't use a converter if there isn't a custom one
if (converter instanceof NoopConverter) {
value = convertValueToType(currentColumnInfo.getType(), formattedValue, currentColumnInfo.getName(), rowNum);
} else {
// Handle any exceptions thrown by the converter - this stops execution of the whole process
try {
value = converter.convert(formattedValue, currentColumnInfo.getName(), rowNum);
} catch(Exception e) {
throw new ZeroCellException(String.format("%s threw an exception while trying to convert value %s ", converter.getClass().getName(), formattedValue), e);
}
}
Field field = type.getDeclaredField(currentColumnInfo.getFieldName());
boolean access = field.isAccessible();
if (! access) {
field.setAccessible(true);
}
field.set(cur, value);
field.setAccessible(field.isAccessible() && access);
} catch (IllegalArgumentException e) {
throw new ZeroCellException(String.format("Failed to write value %s to field %s at row %s", formattedValue, fieldName, rowNum));
} catch (InstantiationException | NoSuchFieldException | IllegalAccessException e) {
LOGGER.error("Failed to set field: {}", fieldName, e);
}
}
public Object convertValueToType(Class> fieldType, String formattedValue, String columnName, int rowNum) {
Object value = null;
if (fieldType == String.class) {
value = String.valueOf(formattedValue);
} else if (fieldType == LocalDateTime.class) {
return Converters.toLocalDateTime.convert(formattedValue, columnName, rowNum);
} else if (fieldType == LocalDate.class) {
return Converters.toLocalDate.convert(formattedValue, columnName, rowNum);
} else if (fieldType == java.sql.Date.class) {
return Converters.toSqlDate.convert(formattedValue, columnName, rowNum);
} else if (fieldType == Timestamp.class) {
return Converters.toSqlTimestamp.convert(formattedValue, columnName, rowNum);
} else if (fieldType == Integer.class || fieldType == int.class) {
return Converters.toInteger.convert(formattedValue, columnName, rowNum);
} else if (fieldType == Long.class || fieldType == long.class) {
return Converters.toLong.convert(formattedValue, columnName, rowNum);
} else if (fieldType == Double.class || fieldType == double.class) {
return Converters.toDouble.convert(formattedValue, columnName, rowNum);
} else if (fieldType == Float.class || fieldType == float.class) {
return Converters.toFloat.convert(formattedValue, columnName, rowNum);
} else if (fieldType == Boolean.class) {
return Converters.toBoolean.convert(formattedValue, columnName, rowNum);
}
return value;
}
@Override
public void headerFooter(String text, boolean b, String tagName) {
// Skip, no headers or footers in CSV
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy