org.sfm.csv.CsvMapperBuilder Maven / Gradle / Ivy
package org.sfm.csv;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.sfm.csv.impl.CellSetter;
import org.sfm.csv.impl.CellSetterFactory;
import org.sfm.csv.impl.CsvMapperImpl;
import org.sfm.csv.impl.DelayedCellSetter;
import org.sfm.csv.impl.DelayedCellSetterFactory;
import org.sfm.csv.impl.DelayedGetter;
import org.sfm.csv.impl.DelegateMarkerDelayedCellSetter;
import org.sfm.csv.impl.DelegateMarkerSetter;
import org.sfm.csv.impl.ParsingContextFactory;
import org.sfm.csv.impl.primitive.BooleanDelayedGetter;
import org.sfm.csv.impl.primitive.ByteDelayedGetter;
import org.sfm.csv.impl.primitive.CharDelayedGetter;
import org.sfm.csv.impl.primitive.DoubleDelayedGetter;
import org.sfm.csv.impl.primitive.FloatDelayedGetter;
import org.sfm.csv.impl.primitive.IntDelayedGetter;
import org.sfm.csv.impl.primitive.LongDelayedGetter;
import org.sfm.csv.impl.primitive.ShortDelayedGetter;
import org.sfm.map.FieldMapperErrorHandler;
import org.sfm.map.MapperBuilderErrorHandler;
import org.sfm.map.MapperBuildingException;
import org.sfm.map.RowHandlerErrorHandler;
import org.sfm.map.impl.*;
import org.sfm.reflect.Getter;
import org.sfm.reflect.Instantiator;
import org.sfm.reflect.InstantiatorFactory;
import org.sfm.reflect.ReflectionService;
import org.sfm.reflect.TypeHelper;
import org.sfm.reflect.ConstructorParameter;
import org.sfm.reflect.meta.*;
import org.sfm.utils.ForEachCallBack;
public class CsvMapperBuilder {
private final Class> SOURCE_UNTYPE = DelayedCellSetter[].class;
@SuppressWarnings("unchecked")
private final Class[]> SOURCE = (Class[]>) SOURCE_UNTYPE;
private FieldMapperErrorHandler fieldMapperErrorHandler = new RethrowFieldMapperErrorHandler();
private MapperBuilderErrorHandler mapperBuilderErrorHandler = new RethrowMapperBuilderErrorHandler();
private RowHandlerErrorHandler rowHandlerErrorHandler = new RethrowRowHandlerErrorHandler();
private final PropertyNameMatcherFactory propertyNameMatcherFactory;
private final Type target;
private final ReflectionService reflectionService;
private final Map aliases;
private final Map> customReaders;
private final PropertyMappingsBuilder propertyMappingsBuilder;
private int syncSetterStart;
private String defaultDateFormat = "yyyy-MM-dd HH:mm:ss";
public CsvMapperBuilder(final Type target) {
this(target, ReflectionService.newInstance());
}
public CsvMapperBuilder(final Type target, ReflectionService reflectionService) {
this(target, (ClassMeta)reflectionService.getClassMeta(target));
}
public CsvMapperBuilder(final Type target, final ClassMeta classMeta) {
this(target, classMeta,
new HashMap(),
new HashMap>(), new DefaultPropertyNameMatcherFactory());
}
public CsvMapperBuilder(final Type target, final ClassMeta classMeta,
Map aliases, Map> customReaders, PropertyNameMatcherFactory propertyNameMatcherFactory) throws MapperBuildingException {
this.target = target;
this.reflectionService = classMeta.getReflectionService();
this.propertyMappingsBuilder = new PropertyMappingsBuilder(classMeta, propertyNameMatcherFactory);
this.propertyNameMatcherFactory = propertyNameMatcherFactory;
this.aliases = aliases;
this.customReaders = customReaders;
}
public final CsvMapperBuilder addMapping(final String columnKey) {
return addMapping(columnKey, propertyMappingsBuilder.size());
}
public final CsvMapperBuilder addMapping(final String columnKey, int columnIndex) {
return addMapping(new CsvColumnKey(columnKey, columnIndex));
}
public final CsvMapperBuilder addMapping(final CsvColumnKey key) {
final CsvColumnKey mappedColumnKey = alias(key);
if (!propertyMappingsBuilder.addProperty(mappedColumnKey)) {
mapperBuilderErrorHandler.propertyNotFound(target, key.getName());
}
return this;
}
private void addMapping(PropertyMeta propertyMeta, final CsvColumnKey key) {
propertyMappingsBuilder.addProperty(key, propertyMeta);
}
private CsvColumnKey alias(CsvColumnKey key) {
if (aliases == null || aliases.isEmpty()) {
return key;
}
String alias = aliases.get(key.getName().toUpperCase());
if (alias == null) {
return key;
}
return key.alias(alias);
}
public void setDefaultDateFormat(String defaultDateFormat) {
this.defaultDateFormat = defaultDateFormat;
}
public final CsvMapper mapper() {
return new CsvMapperImpl(getInstantiator(), buildDelayedSetters(), getSetters(), getKeys(), getParserContextFactory(), fieldMapperErrorHandler, rowHandlerErrorHandler);
}
private CsvColumnKey[] getKeys() {
return propertyMappingsBuilder.getKeys().toArray(new CsvColumnKey[propertyMappingsBuilder.size()]);
}
private ParsingContextFactory getParserContextFactory() {
final ParsingContextFactory pcf = new ParsingContextFactory(propertyMappingsBuilder.size());
propertyMappingsBuilder.forEachProperties(new ForEachCallBack>() {
@Override
public void handle(PropertyMapping propMapping, int i) {
if (propMapping == null) return;
PropertyMeta prop = propMapping.getPropertyMeta();
if (prop != null) {
Class> target;
if (prop instanceof SubPropertyMeta) {
target = TypeHelper.toClass(((SubPropertyMeta, ?>) prop)
.getFinalType());
} else {
target = TypeHelper.toClass(prop.getType());
}
if (Date.class.equals(target)) {
pcf.setDateFormat(i, getDateFormat(i));
}
}
}
});
return pcf;
}
private String getDateFormat(int i) {
return defaultDateFormat;
}
private Instantiator[], T> getInstantiator() throws MapperBuildingException {
InstantiatorFactory instantiatorFactory = reflectionService.getInstantiatorFactory();
try {
return instantiatorFactory.getInstantiator(SOURCE, target, propertyMappingsBuilder, buildConstructorParametersDelayedCellSetter(), customReaders.isEmpty() );
} catch(Exception e) {
throw new MapperBuildingException(e.getMessage(), e);
}
}
private Map[], ?>> buildConstructorParametersDelayedCellSetter() {
final Map[], ?>> constructorInjections = new HashMap[], ?>>();
propertyMappingsBuilder.forEachProperties(new ForEachCallBack>() {
@Override
public void handle(PropertyMapping propMapping, int index) {
if(propMapping == null) return;
PropertyMeta meta = propMapping.getPropertyMeta();
if (meta == null) return;
CsvColumnKey key = propMapping.getColumnKey();
if (meta.isConstructorProperty()) {
syncSetterStart = index + 1;
constructorInjections.put(((ConstructorPropertyMeta) meta).getConstructorParameter(), newDelayedGetter(key, meta.getType()));
} else if (meta.isSubProperty()) {
SubPropertyMeta subMeta = (SubPropertyMeta) meta;
if (subMeta.getOwnerProperty().isConstructorProperty()) {
ConstructorPropertyMeta, ?> constPropMeta = (ConstructorPropertyMeta, ?>) subMeta.getOwnerProperty();
if (!constructorInjections.containsKey(constPropMeta.getConstructorParameter())) {
constructorInjections.put(constPropMeta.getConstructorParameter(), newDelayedGetter(key, constPropMeta.getType()));
}
syncSetterStart = index + 1;
}
}
}
});
return constructorInjections;
}
private Getter[], ?> newDelayedGetter(CsvColumnKey key, Type type) {
Class> clazz = TypeHelper.toClass(type);
Getter[], ?> getter;
int columnIndex = key.getIndex();
if (clazz.isPrimitive() && ! customReaders.containsKey(key.getName())) {
if (boolean.class.equals(clazz)) {
getter = new BooleanDelayedGetter(columnIndex);
} else if (byte.class.equals(clazz)) {
getter = new ByteDelayedGetter(columnIndex);
} else if (char.class.equals(clazz)) {
getter = new CharDelayedGetter(columnIndex);
} else if (short.class.equals(clazz)) {
getter = new ShortDelayedGetter(columnIndex);
} else if (int.class.equals(clazz)) {
getter = new IntDelayedGetter(columnIndex);
} else if (long.class.equals(clazz)) {
getter = new LongDelayedGetter(columnIndex);
} else if (float.class.equals(clazz)) {
getter = new FloatDelayedGetter(columnIndex);
} else if (double.class.equals(clazz)) {
getter = new DoubleDelayedGetter(columnIndex);
} else {
throw new IllegalArgumentException("Unexpected primitive " + clazz);
}
} else {
getter = new DelayedGetter(columnIndex);
}
return getter;
}
@SuppressWarnings({ "unchecked" })
private DelayedCellSetterFactory[] buildDelayedSetters() {
final Map> delegateMapperBuilders = new HashMap>();
final List> delayedSetters = new ArrayList>(syncSetterStart);
propertyMappingsBuilder.forEachProperties(new ForEachCallBack>() {
CellSetterFactory cellSetterFactory = new CellSetterFactory(customReaders);
@Override
public void handle(PropertyMapping propMapping, int index) {
if (propMapping != null) {
PropertyMeta prop = propMapping.getPropertyMeta();
CsvColumnKey key = propMapping.getColumnKey();
if (prop != null) {
if (prop.isConstructorProperty()) {
delayedSetters.add((DelayedCellSetterFactory)cellSetterFactory.getDelayedCellSetter(prop.getType(), index, key.getName()));
} else if (prop.isSubProperty()) {
addSubProperty(delegateMapperBuilders, delayedSetters, prop, key);
}else {
delayedSetters.add(cellSetterFactory.getDelayedCellSetter(prop.getType(), prop.getSetter(), index, key.getName()));
}
} else {
delayedSetters.add(null);
}
} else {
delayedSetters.add(null);
}
}
private void addSubProperty(
Map> delegateMapperBuilders,
List> delayedSetters,
PropertyMeta prop, CsvColumnKey key) {
SubPropertyMeta subPropertyMeta = (SubPropertyMeta)prop;
final PropertyMeta propOwner = subPropertyMeta.getOwnerProperty();
CsvMapperBuilder delegateMapperBuilder = (CsvMapperBuilder
) delegateMapperBuilders .get(propOwner.getName());
if (delegateMapperBuilder == null) {
delegateMapperBuilder = new CsvMapperBuilder
(propOwner.getType(), propOwner.getClassMeta(), aliases, customReaders, propertyNameMatcherFactory);
delegateMapperBuilders.put(propOwner.getName(), delegateMapperBuilder);
}
delegateMapperBuilder.addMapping(subPropertyMeta.getSubProperty(), key);
delayedSetters.add(null);
}
}, 0, syncSetterStart);
final Map> mappers = new HashMap>();
propertyMappingsBuilder.forEachProperties(new ForEachCallBack>() {
@Override
public void handle(PropertyMapping propMapping, int index) {
if (propMapping == null) return;
PropertyMeta prop = propMapping.getPropertyMeta();
if (prop.isSubProperty()) {
addSubPropertyDelayedSetter(delegateMapperBuilders, delayedSetters, mappers, index, prop);
}
}
private void addSubPropertyDelayedSetter(
Map> delegateMapperBuilders,
List> delayedSetters,
Map> mappers, int setterIndex,
PropertyMeta prop) {
PropertyMeta subProp = ((SubPropertyMeta) prop).getOwnerProperty();
final String propName = subProp.getName();
CsvMapper mapper = (CsvMapper
) mappers.get(propName);
if (mapper == null) {
CsvMapperBuilder
delegateMapperBuilder = (CsvMapperBuilder
) delegateMapperBuilders.get(propName);
mapper = delegateMapperBuilder.mapper();
mappers.put(propName, mapper);
}
if (subProp instanceof ConstructorPropertyMeta) {
delayedSetters.set(setterIndex , new DelegateMarkerDelayedCellSetter(mapper));
} else {
delayedSetters.set(setterIndex , new DelegateMarkerDelayedCellSetter(mapper, subProp.getSetter()));
}
}
}, 0, syncSetterStart);
return delayedSetters.toArray(new DelayedCellSetterFactory[syncSetterStart]);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private CellSetter[] getSetters() {
final Map> delegateMapperBuilders = new HashMap>();
final List> setters = new ArrayList>();
propertyMappingsBuilder.forEachProperties(new ForEachCallBack>() {
CellSetterFactory cellSetterFactory = new CellSetterFactory(customReaders);
@Override
public void handle(PropertyMapping propMapping, int index) {
if (propMapping != null) {
PropertyMeta prop = propMapping.getPropertyMeta();
if (prop != null) {
CsvColumnKey key = propMapping.getColumnKey();
if (prop.isConstructorProperty()) {
throw new IllegalStateException("Unexpected ConstructorPropertyMeta at " + index);
} else if (prop.isSubProperty()) {
final PropertyMeta, ?> powner = ((SubPropertyMeta)prop).getOwnerProperty();
CsvMapperBuilder> delegateMapperBuilder = delegateMapperBuilders .get(powner.getName());
if (delegateMapperBuilder == null) {
delegateMapperBuilder = new CsvMapperBuilder(powner.getType(), powner.getClassMeta(), aliases, customReaders, propertyNameMatcherFactory);
delegateMapperBuilders.put(powner.getName(), delegateMapperBuilder);
}
delegateMapperBuilder.addMapping(((SubPropertyMeta) prop).getSubProperty(), key);
setters.add(null);
} else {
setters.add(cellSetterFactory.getCellSetter(prop.getType(), prop.getSetter(), index, key.getName()));
}
} else {
setters.add(null);
}
} else {
setters.add(null);
}
}
}, syncSetterStart);
propertyMappingsBuilder.forEachProperties(new ForEachCallBack>() {
final Map> mappers = new HashMap>();
@Override
public void handle(PropertyMapping propMapping, int index) {
if (propMapping == null) return;
PropertyMeta prop = propMapping.getPropertyMeta();
if (prop instanceof SubPropertyMeta) {
final String propName = ((SubPropertyMeta)prop).getOwnerProperty().getName();
CsvMapper> mapper = mappers.get(propName);
if (mapper == null) {
CsvMapperBuilder> delegateMapperBuilder = delegateMapperBuilders .get(propName);
mapper = delegateMapperBuilder.mapper();
mappers.put(propName, mapper);
}
setters.set(index , new DelegateMarkerSetter(mapper, ((SubPropertyMeta) prop).getOwnerProperty().getSetter()));
}
}
}, syncSetterStart);
return setters.toArray(new CellSetter[setters.size()]);
}
public final CsvMapperBuilder fieldMapperErrorHandler(final FieldMapperErrorHandler errorHandler) {
fieldMapperErrorHandler = errorHandler;
return this;
}
public final CsvMapperBuilder mapperBuilderErrorHandler(final MapperBuilderErrorHandler errorHandler) {
mapperBuilderErrorHandler = errorHandler;
return this;
}
}