package org.openlca.simapro.csv; import java.io.File; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Objects; import org.openlca.simapro.csv.io.BlockReader; import org.openlca.simapro.csv.io.FileHeaderReader; import org.openlca.simapro.csv.io.ModelReader; import org.openlca.simapro.csv.model.FileHeader; import org.openlca.simapro.csv.model.annotations.BlockHandler; import org.openlca.simapro.csv.model.annotations.BlockModel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class SimaProCSV { private Logger log = LoggerFactory.getLogger(getClass()); private final File file; private final Object handler; private HashMap<Class<?>, List<Method>> methodHandlers = new HashMap<>(); private SimaProCSV(File file, Object handler) { this.file = file; this.handler = handler; } public static void parse(File file, Object handler) throws Exception { if (file == null || handler == null) return; new SimaProCSV(file, handler).parse(); } private void parse() throws Exception { log.trace("parse file {} with handler {}", file, handler); registerMethods(); if (methodHandlers.isEmpty()) { log.warn("no method handlers registerred, do nothing"); return; } CsvConfig config = readConfig(file); try (BlockReader reader = new BlockReader(file); ModelReader modelReader = createModelReader(reader, config)) { Object model = null; while ((model = modelReader.read()) != null) { handleModel(model); } } } private ModelReader createModelReader(BlockReader blockReader, CsvConfig config) { Class<?>[] classes = new Class<?>[methodHandlers.size()]; int i = 0; for (Class<?> clazz : methodHandlers.keySet()) { classes[i] = clazz; i++; } log.trace("create a model reader with {} classes", i); return new ModelReader(blockReader, config, classes); } private void registerMethods() { log.trace("register method handlers"); for (Method method : handler.getClass().getDeclaredMethods()) { if (!method.isAnnotationPresent(BlockHandler.class)) continue; List<Class<?>> paramTypes = getParameterTypes(method); if (paramTypes.isEmpty()) continue; method.setAccessible(true); for (Class<?> paramType : paramTypes) { log.trace("register method {} for type {}", method, paramType); List<Method> handlers = methodHandlers.get(paramType); if (handlers == null) { handlers = new ArrayList<>(); methodHandlers.put(paramType, handlers); } handlers.add(method); } } } private List<Class<?>> getParameterTypes(Method method) { Class<?>[] paramTypes = method.getParameterTypes(); if (paramTypes.length != 1) { logInvalidMethod(method); return Collections.emptyList(); } Class<?> paramType = paramTypes[0]; BlockHandler blockHandler = method.getAnnotation(BlockHandler.class); List<Class<?>> result = new ArrayList<>(); Class<?>[] subTypes = blockHandler.subTypes(); if (subTypes == null || subTypes.length == 0) { if (!paramType.isAnnotationPresent(BlockModel.class)) logInvalidMethod(method); else result.add(paramType); } else { for (Class<?> subType : subTypes) { if (!paramType.isAssignableFrom(subType)) logInvalidMethod(method); else if (!subType.isAnnotationPresent(BlockModel.class)) logInvalidMethod(method); else result.add(subType); } } return result; } private void logInvalidMethod(Method method) { log.warn("The method {} is declared as block handler but is not valid." + "A block handler takes exactly 1 argument which must " + "be a class annotated with @BlockModel or a super type " + " of the classes given in the @BlockModel annotation " + " which itself must be annotated with @BlockModel", method); } private void handleModel(Object model) throws Exception { List<Method> methods = methodHandlers.get(model.getClass()); if (methods == null) return; for (Method method : methods) { method.invoke(handler, model); } } private CsvConfig readConfig(File file) { CsvConfig config = CsvConfig.getDefault(); try { FileHeaderReader reader = new FileHeaderReader(file); FileHeader header = reader.read(); if (header.getShortDateFormat() != null) config.setDateFormat(header.getShortDateFormat()); if (Objects.equals("Semicolon", header.getCsvSeparator())) config.setSeparator(';'); else if (Objects.equals("Comma", header.getCsvSeparator())) config.setSeparator(','); } catch (Exception e) { log.error("failed to read header CSV entries from " + file, e); } return config; } }