CSVImpl.java
package org.cyclopsgroup.caff.format;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.AccessibleObject;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.cyclopsgroup.caff.CharIterator;
import org.cyclopsgroup.caff.conversion.AnnotatedConverter;
import org.cyclopsgroup.caff.conversion.Converter;
import org.cyclopsgroup.caff.ref.ValueReference;
import org.cyclopsgroup.caff.ref.ValueReferenceScanner;
/**
* Internal class that really does CSV format and parsing
*
* @author <a href="mailto:jiaqi.guo@gmail.com">Jiaqi Guo</a>
* @param <T> Type of bean to convert from/to
*/
class CSVImpl<T> {
private class Slot {
private final Converter<Object> converter;
private final CSVField fieldAnnotation;
private final ValueReference<T> reference;
private Slot(
ValueReference<T> reference, Converter<Object> converter, CSVField fieldAnnotation) {
this.reference = reference;
this.converter = converter;
this.fieldAnnotation = fieldAnnotation;
}
private CharSequence read(T bean) {
return converter.toCharacters(reference.readValue(bean));
}
private void write(T bean, CharSequence value) {
reference.writeValue(converter.fromCharacters(value), bean);
}
}
private static final char QUOTE = '"';
private final int fields;
private final Map<Integer, Slot> slots;
/**
* @param beanType Type of bean to parse or format
*/
CSVImpl(final Class<T> beanType) {
CSVType typeAnnotation = beanType.getAnnotation(CSVType.class);
if (typeAnnotation == null) {
throw new IllegalArgumentException(
"Type " + beanType + " isn't annotated with " + CSVType.class);
}
fields = typeAnnotation.fields();
if (fields <= 0) {
throw new IllegalArgumentException(
"Type " + beanType + " defines invalid number of CSV fields: " + fields);
}
ValueReferenceScanner<T> scanner = new ValueReferenceScanner<T>(beanType);
final Map<Integer, Slot> slots = new HashMap<Integer, Slot>();
scanner.scanForAnnotation(
CSVField.class,
new ValueReferenceScanner.Listener<T, CSVField>() {
@SuppressWarnings("unchecked")
@Override
public void handleReference(
ValueReference<T> reference, CSVField field, AccessibleObject access) {
Slot slot =
new Slot(
reference,
new AnnotatedConverter<Object>((Class<Object>) reference.getType(), access),
field);
slots.put(field.position(), slot);
}
});
this.slots = Collections.unmodifiableMap(slots);
}
/**
* @param bean Bean to parse
* @param in Char iterator of input
* @throws IOException If IO writing fails
*/
void populate(final T bean, CharIterator in) throws IOException {
CSVParser parser =
new CSVParser() {
@Override
protected void handleField(int position, CharSequence content) throws IOException {
Slot slot = slots.get(position);
if (slot != null) {
slot.write(bean, content);
}
}
};
parser.parse(in);
}
/**
* @param bean Bean to format
* @param out Output writer
* @throws IOException If writing fails
*/
void print(T bean, Writer out) throws IOException {
for (int i = 0; i < fields; i++) {
if (i != 0) {
out.write(',');
}
Slot slot = slots.get(i);
if (slot == null) {
continue;
}
String value = slot.read(bean).toString();
boolean quoteRequired = slot.fieldAnnotation.alwaysQuote();
if (value.indexOf(QUOTE) > -1) {
quoteRequired = true;
value = value.toString().replaceAll("" + QUOTE, "" + QUOTE + QUOTE);
}
if (quoteRequired) {
out.write(QUOTE);
out.write(value);
out.write(QUOTE);
} else {
out.write(value);
}
// TODO Consider truncation
}
}
}