package com.googlecode.jcsv.reader.internal;
import java.lang.reflect.Field;
import com.googlecode.jcsv.annotations.MapToColumn;
import com.googlecode.jcsv.annotations.ValueProcessor;
import com.googlecode.jcsv.annotations.internal.ValueProcessorProvider;
import com.googlecode.jcsv.reader.CSVEntryParser;
/**
* Parses a csv entry, based on an annotated class.
*
* @author Eike Bergmann
*
* @param <E>
* the type of the csv entries
*/
public class AnnotationEntryParser<E> implements CSVEntryParser<E> {
private final Class<E> clazz;
private final ValueProcessorProvider provider;
/**
* Constructs a AnnotationEntryParser for type E.
*
* @param clazz
* the annotated class, and the class of the csv entries
*/
public AnnotationEntryParser(Class<E> clazz, ValueProcessorProvider provider) {
this.clazz = clazz;
this.provider = provider;
}
/**
* {@link CSVEntryParser#parseEntry(String...)}
*/
@Override
public E parseEntry(String... data) {
// create the instance
E entry = newClassIntance();
// fill the object with data
fillObject(entry, data);
return entry;
}
/**
* Creates a new instance of type E.
*
* @throws RuntimeException
* thrown if the class can not be instantiated or accessed
* @return the new instance
*/
private E newClassIntance() {
E entry;
try {
entry = clazz.newInstance();
} catch (InstantiationException ie) {
throw new RuntimeException(String.format("can not instantiate class %s", clazz.getName()), ie);
} catch (IllegalAccessException iae) {
throw new RuntimeException(String.format("can not access class %s", clazz.getName()), iae);
}
return entry;
}
/**
* Fill the instance of E with the data of the csv row.
*
* This method iterates over the annotations that are present in the target
* class and uses the appropriate value processors to set the data.
*
* @param entry
* The created instance of E
* @param data
* The data that should be set
*/
private void fillObject(E entry, String[] data) {
for (Field field : entry.getClass().getDeclaredFields()) {
// check if there is a MapToColumn Annotation
MapToColumn mapAnnotation = field.getAnnotation(MapToColumn.class);
if (mapAnnotation != null) {
// read the annotation column
int column = mapAnnotation.column();
// read the annotation type. If type is Default.class, then
// the type of the field will be used.
Class<?> type;
if (mapAnnotation.type().equals(MapToColumn.Default.class)) {
// use the field type
type = field.getType();
} else {
// use the annotated type
type = mapAnnotation.type();
}
// load the appropriate value processor
ValueProcessor<?> vp = provider.getValueProcessor(type);
// use the value processor to convert the string data
Object value = vp.processValue(data[column]);
// make the field accessible and remember its state
boolean wasAccessible = field.isAccessible();
field.setAccessible(true);
// try to set the field's value
try {
field.set(entry, value);
} catch (IllegalArgumentException iae) {
throw new RuntimeException(String.format("can not set value %s for type %s", value, type), iae);
} catch (IllegalAccessException iae) {
throw new RuntimeException(String.format("can not access field %s", field), iae);
}
// re-set the accessible flag
field.setAccessible(wasAccessible);
}
}
}
}