/* Copyright 2012 GanttProject Team This file is part of GanttProject, an opensource project management tool. GanttProject is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. GanttProject is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GanttProject. If not, see <http://www.gnu.org/licenses/>. */ package biz.ganttproject.impex.csv; import biz.ganttproject.core.time.TimeUnitStack; import com.google.common.base.Function; import com.google.common.base.Strings; import com.google.common.base.Supplier; import com.google.common.collect.Collections2; import com.google.common.collect.Lists; import net.sourceforge.ganttproject.CustomPropertyClass; import net.sourceforge.ganttproject.CustomPropertyManager; import net.sourceforge.ganttproject.GPLogger; import net.sourceforge.ganttproject.io.CSVOptions; import net.sourceforge.ganttproject.language.GanttLanguage; import net.sourceforge.ganttproject.resource.HumanResourceManager; import net.sourceforge.ganttproject.roles.RoleManager; import net.sourceforge.ganttproject.task.TaskManager; import net.sourceforge.ganttproject.util.collect.Pair; import org.apache.commons.csv.CSVFormat; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import static net.sourceforge.ganttproject.GPLogger.debug; import static net.sourceforge.ganttproject.util.FileUtil.getExtension; /** * Handles opening CSV and XLS files. */ public class GanttCSVOpen { static Collection<String> getFieldNames(Enum... fieldsEnum) { return Collections2.transform(Arrays.asList(fieldsEnum), new Function<Enum, String>() { @Override public String apply(Enum input) { return input.toString(); } }); } static final GanttLanguage language = GanttLanguage.getInstance(); private final Supplier<InputStream> myInputSupplier; private final SpreadsheetFormat myFormat; private final List<RecordGroup> myRecordGroups; private int mySkippedLine; private CSVOptions myCsvOptions; public GanttCSVOpen(Supplier<InputStream> inputSupplier, SpreadsheetFormat format, RecordGroup... groups) { myInputSupplier = inputSupplier; myRecordGroups = Lists.newArrayList(); for (RecordGroup group : groups) { if (group != null) { myRecordGroups.add(group); } } myFormat = format; } public GanttCSVOpen(Supplier<InputStream> inputSupplier, SpreadsheetFormat format, final TaskManager taskManager, final HumanResourceManager resourceManager, RoleManager roleManager, TimeUnitStack timeUnitStack) { this(inputSupplier, format, createTaskRecordGroup(taskManager, resourceManager, timeUnitStack), createResourceRecordGroup(resourceManager, roleManager)); } public GanttCSVOpen(final File file, final TaskManager taskManager, final HumanResourceManager resourceManager, final RoleManager roleManager, TimeUnitStack timeUnitStack) { this(() -> { try { return new FileInputStream(file); } catch (FileNotFoundException e) { throw new RuntimeException(e); } }, createSpreadsheetFormat(file), taskManager, resourceManager, roleManager, timeUnitStack); } private static RecordGroup createTaskRecordGroup(final TaskManager taskManager, final HumanResourceManager resourceManager, TimeUnitStack timeUnitStack) { return new TaskRecords(taskManager, resourceManager, timeUnitStack); } protected static void createCustomProperties(Collection<String> customFields, CustomPropertyManager customPropertyManager) { for (String name : customFields) { customPropertyManager.createDefinition(name, CustomPropertyClass.TEXT.getID(), name, null); } } private static RecordGroup createResourceRecordGroup(HumanResourceManager resourceManager, RoleManager roleManager) { return resourceManager == null ? null : new ResourceRecords(resourceManager, roleManager); } private int doLoad(SpreadsheetReader reader, int numGroup, int linesToSkip) { final Logger logger = GPLogger.getLogger(GanttCSVOpen.class); int lineCounter = 0; RecordGroup currentGroup = myRecordGroups.get(numGroup); boolean searchHeader = currentGroup.getHeader() == null; if (searchHeader) { debug(logger, "[CSV] Searching for a header of %s", currentGroup); } else { debug(logger, "[CSV] Expecting to read records of group %s", currentGroup); numGroup++; } for (Iterator<SpreadsheetRecord> it = reader.iterator(); it.hasNext(); ) { SpreadsheetRecord record = it.next(); lineCounter++; if (linesToSkip-- > 0) { continue; } if (record.size() == 0 || record.size() == 1 && Strings.isNullOrEmpty(record.get(0))) { // If line is empty then current record group is probably finished. // Let's search for the next group header. searchHeader = true; continue; } if (searchHeader) { if (numGroup < myRecordGroups.size()) { debug(logger, "%s\n", record); RecordGroup nextGroup = myRecordGroups.get(numGroup); // Record is not empty and we're searching for header. if (nextGroup.isHeader(record)) { debug(logger, "[CSV] ^^^ This seems to be a header"); nextGroup.setHeader(Lists.newArrayList(record.iterator())); return lineCounter; } } } if (currentGroup.doProcess(record)) { searchHeader = false; } else { mySkippedLine++; } } return 0; } /** * Create tasks from file. * * @throws IOException on parse error or input read-failure */ public List<Pair<Level, String>> load() throws IOException { final List<Pair<Level, String>> errors = Lists.newArrayList(); for (RecordGroup group : myRecordGroups) { group.setErrorOutput(errors); } int idxCurrentGroup = 0; int idxNextGroup; int skipHeadLines = 0; List<String> headers; do { idxNextGroup = idxCurrentGroup; RecordGroup currentGroup = myRecordGroups.get(idxCurrentGroup); headers = currentGroup.getHeader(); if (headers != null) { idxNextGroup++; } try (SpreadsheetReader reader = createReader(myInputSupplier.get(), headers)) { skipHeadLines = doLoad(reader, idxCurrentGroup, skipHeadLines); } idxCurrentGroup = idxNextGroup; } while (skipHeadLines > 0); for (RecordGroup group : myRecordGroups) { group.postProcess(); } return errors; } int getSkippedLineCount() { return mySkippedLine; } public void setOptions(CSVOptions csvOptions) { myCsvOptions = csvOptions; } private SpreadsheetReader createReader(InputStream is, List<String> headers) throws IOException { switch (myFormat) { case CSV: return new CsvReaderImpl(is, createCSVFormat(headers)); case XLS: return new XlsReaderImpl(is, headers); default: throw new IllegalArgumentException("Unsupported format: " + myFormat); } } private CSVFormat createCSVFormat(List<String> headers) { CSVFormat format = CSVFormat.DEFAULT.withIgnoreEmptyLines(false).withIgnoreSurroundingSpaces(true); if (myCsvOptions != null) { format = format.withDelimiter(myCsvOptions.sSeparatedChar.charAt(0)).withQuote(myCsvOptions.sSeparatedTextChar.charAt(0)); } if (headers != null) { format = format.withHeader(headers.toArray(new String[0])); } return format; } private static SpreadsheetFormat createSpreadsheetFormat(File file) { String extension = getExtension(file); if (extension.isEmpty()) { throw new IllegalArgumentException("No file extension!"); } return SpreadsheetFormat.getSpreadsheetFormat(extension); } }