On my job I had the task to process multiple excel sheets, so I tried to make a generic component to make the conversion between the cell values and a java object type.
The first thing that came to mind was a generic interface that will handle the processing of a particular column:
public interface ExcelColumnProcessor<T> {
T process(Cell cell);
}
So, for example, I can have:
public class SimpleDateProcessor implements ExcelColumnProcessor<Date> {
@Override
public Date process(Cell cell) {
return DateUtils.format(DateUtils.DateFormat.DD_slash_MM_slash_YYYY, cell.getStringCellValue().trim());
}
}
which whill handle the processing of values like '01/01/2015', and so forth.
The next problem was, how to tie the column with the processor. For this I made an annotation:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ProcessWith {
long columnIndex();
Class<? extends ExcelColumnProcessor<?>> processorClass();
}
The idea was that this annotation could be use like the following:
public class MyExcelRow {
@ProcessWith(columnIndex = 1, processorClass = SimpleStringProcessor.class)
private String someField;
@ProcessWith(columnIndex = 2, processorClass = SimpleDateProcessor.class)
private Date someDate;
}
The class MyExcelRow
would represent a row of a particular excel sheet, and some of it's fields (could be all or not, it's just a pojo) represent the column values in that particular row.
Now, I needed something to handle the processing itself, to tie everything together, so I made this:
public final class Processor<T> {
private Map<Long, MetaData> processors = new ConcurrentHashMap<>();
private Processor(Map<Long, MetaData> processors) {
this.processors = processors;
}
private void checkObjectIsNotNull(T objectToProcess) {
if (objectToProcess == null) {
throw new IllegalArgumentException("The object cannot be null");
}
}
public void process(T objectToProcess, Cell cell) {
checkObjectIsNotNull(objectToProcess);
if (cell == null) {
throw new IllegalArgumentException("The cell cannot be null");
}
MetaData metaData = processors.get(cell.getColumnIndex());
try {
Method method = objectToProcess.getClass().getMethod(metaData.getMethodToExecute(),
metaData.getFieldType());
method.invoke(objectToProcess, metaData.getProcessorToUse().process(cell));
} catch (Throwable t) {
throw new IllegalStateException(t);
}
}
public void process(T objectToProcess, Cell... cells) {
checkObjectIsNotNull(objectToProcess);
if (cells == null || cells.length == 0) {
throw new IllegalArgumentException("The cells cannot be empty");
}
for (Cell cell : cells) {
process(objectToProcess, cell);
}
}
public void process(T objectToProcess, Iterator<Cell> cellIterator) {
checkObjectIsNotNull(objectToProcess);
if (cellIterator == null) {
throw new IllegalArgumentException("The iterator cannot be null");
}
while (cellIterator.hasNext()) {
process(objectToProcess, cellIterator.next());
}
}
public static <T> Processor<T> newInstance(Class<T> processorClass) {
if (processorClass == null) {
throw new IllegalArgumentException("The class cannot be null");
}
Map<Long, MetaData> processors = new ConcurrentHashMap<>();
for (Field field : processorClass.getFields()) {
if (field.isAnnotationPresent(ProcessWith.class)) {
ProcessWith processWith = field.getAnnotation(ProcessWith.class);
Long columnIndex = processWith.columnIndex();
Class<? extends ExcelColumnProcessor<?>> clazz = processWith.processorClass();
Class<?> fieldType = field.getType();
try {
MetaData metaData = new MetaData();
metaData.setFieldType(fieldType);
String fieldName = field.getName();
metaData.setMethodToExecute(
"set" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1));
metaData.setProcessorToUse(clazz.newInstance());
processors.put(columnIndex, metaData);
} catch (Throwable t) {
throw new IllegalStateException(t);
}
}
}
Processor<T> processor = new Processor<>(processors);
return processor;
}
private static class MetaData {
private String methodToExecute;
private Class<?> fieldType;
private ExcelColumnProcessor<?> processorToUse;
public String getMethodToExecute() {
return methodToExecute;
}
public void setMethodToExecute(String methodToExecute) {
this.methodToExecute = methodToExecute;
}
public Class<?> getFieldType() {
return fieldType;
}
public void setFieldType(Class<?> fieldType) {
this.fieldType = fieldType;
}
public ExcelColumnProcessor<?> getProcessorToUse() {
return processorToUse;
}
public void setProcessorToUse(ExcelColumnProcessor<?> processorToUse) {
this.processorToUse = processorToUse;
}
}
}
The factory method looks for the fields marked with the annotation and saves the information to process that field in a MetaData
object. Then, when the actual processing needs to be done I need the object where to inject the values from the cells of the excel and the cell itself.
I'm relying on the notion that the object in which to inject the values has mutators for the fields that need processing. Also, I'm always instantiating a processor class for each field, which it isn't the best right now because some of the processors might be reused for multiple columns.
I'm concerned about performance. I've read that using reflection usually is pretty slow, so I was wondering if someone knows a better way to do this.
process()
methods? That is, is there a use case other than "populate the object with the entire row's values"? \$\endgroup\$