package org.openlca.simapro.csv.io; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import org.openlca.simapro.csv.CsvConfig; import org.openlca.simapro.csv.CsvUtils; import org.openlca.simapro.csv.model.Block; import org.openlca.simapro.csv.model.IDataRow; import org.openlca.simapro.csv.model.Section; import org.openlca.simapro.csv.model.annotations.BlockRows; import org.openlca.simapro.csv.model.annotations.SectionRow; import org.openlca.simapro.csv.model.annotations.SectionRows; import org.openlca.simapro.csv.model.annotations.SectionValue; import org.openlca.simapro.csv.model.enums.ValueEnum; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class BlockUnmarshaller { private Logger log = LoggerFactory.getLogger(getClass()); private final CsvConfig config; private Block block; private Object model; public BlockUnmarshaller(CsvConfig config) { this.config = config; } public <T> T unmarshall(Block block, Class<T> clazz) throws Exception { if (block == null || clazz == null) return null; log.trace("unmarshall block {} to class {}", block, clazz); T modelBlock = clazz.newInstance(); this.model = modelBlock; this.block = block; for (Field field : clazz.getDeclaredFields()) { if (field.isAnnotationPresent(BlockRows.class)) setBlockRows(field); if (field.isAnnotationPresent(SectionValue.class)) setSectionValue(field); if (field.isAnnotationPresent(SectionRow.class)) setSectionRow(field); if (field.isAnnotationPresent(SectionRows.class)) setSectionRows(field); } this.model = null; this.block = null; return modelBlock; } private void setSectionRow(Field field) throws Exception { SectionRow sectionRow = field.getAnnotation(SectionRow.class); String sectionHeader = sectionRow.value(); Section section = block.getSection(sectionHeader); if (section == null || section.dataRows.isEmpty()) return; Class<?> type = field.getType(); if (!(IDataRow.class.isAssignableFrom(type))) { logNoSectionList(field); return; } field.setAccessible(true); IDataRow instance = (IDataRow) type.newInstance(); String row = section.dataRows.get(0); instance.fill(row, config); field.set(model, instance); } private void setBlockRows(Field field) { if (field == null) return; setDataRows(field, block.dataRows); } private void setSectionRows(Field field) { if (field == null) return; SectionRows rows = field.getAnnotation(SectionRows.class); Section section = block.getSection(rows.value()); if (section == null || section.dataRows.isEmpty()) return; setDataRows(field, section.dataRows); } @SuppressWarnings({ "rawtypes", "unchecked" }) private void setDataRows(Field field, List<String> lines) { try { field.setAccessible(true); List list = getListField(field); if (list == null) return; Class<?> argClass = getRowClass(field); if (argClass == null) return; for (String line : lines) { IDataRow row = (IDataRow) argClass.newInstance(); row.fill(line, config); list.add(row); } } catch (Exception e) { log.error("failed to set data rows to field " + field, e); } } @SuppressWarnings("rawtypes") private List getListField(Field field) throws Exception { Object fieldContent = field.get(model); if (!(fieldContent instanceof List)) { logNoSectionList(field); return null; } List list = (List) fieldContent; return list; } private Class<?> getRowClass(Field field) { Type type = field.getGenericType(); if (!(type instanceof ParameterizedType)) { logNoSectionList(field); return null; } ParameterizedType pType = (ParameterizedType) type; Type[] typeArgs = pType.getActualTypeArguments(); if (typeArgs == null || typeArgs.length != 1) { logNoSectionList(field); return null; } Type arg = typeArgs[0]; if (!(arg instanceof Class)) { logNoSectionList(field); return null; } Class<?> argClass = (Class<?>) arg; if (!(IDataRow.class.isAssignableFrom(argClass))) { logNoSectionList(field); return null; } return argClass; } private void logNoSectionList(Field field) { log.error( "The field {} is not valid for section or block rows: it must be a" + " live list with a declared type argument that is a " + " class which implements IDataRow; e.g. List<Quantity>"); } private void setSectionValue(Field field) { if (field == null) return; try { SectionValue sectionValue = field.getAnnotation(SectionValue.class); String sectionHeader = sectionValue.value(); Section section = block.getSection(sectionHeader); if (section == null || section.dataRows.isEmpty()) return; String val = section.dataRows.get(0); if (val.isEmpty()) return; field.setAccessible(true); setFieldValue(field, val); } catch (Exception e) { log.error("failed to set value on field " + field, e); } } private void setFieldValue(Field field, String val) throws IllegalAccessException, Exception { Class<?> type = field.getType(); if (type.equals(String.class)) field.set(model, CsvUtils.strip(val)); else if (type.equals(Boolean.class)) setBooleanValue(field, val); else if (type.equals(Date.class)) setDateValue(field, val); else if (ValueEnum.class.isAssignableFrom(type)) setEnumValue(field, type, val); else log.error("at field: {} with value: {}; " + "can only set section values " + "to strings, booleans, dates and " + "enumerations types that implement ValueEnum", field, val); } private void setEnumValue(Field field, Class<?> type, String val) throws Exception { if (val == null) return; for (Object o : type.getEnumConstants()) { if (!(o instanceof ValueEnum)) continue; ValueEnum e = (ValueEnum) o; if (val.equalsIgnoreCase(e.getValue())) { field.set(model, e); return; } } log.error("did not find an enum value for {} at field {}", val, field); } private void setBooleanValue(Field field, String val) throws Exception { String lowerVal = val.toLowerCase(); if (lowerVal.equals("yes") || lowerVal.equals(true) || lowerVal.equals("1")) field.set(model, Boolean.TRUE); else field.set(model, Boolean.FALSE); } private void setDateValue(Field field, String val) throws Exception { if (config.getDateFormat() == null) { log.warn("no date-format given in CSV configuration; cannot set " + "date values"); return; } SimpleDateFormat format = new SimpleDateFormat(config.getDateFormat()); Date date = format.parse(val); field.set(model, date); } }