/*
Copyright 2014 BarD Software s.r.o
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.model.task.TaskDefaultColumn;
import biz.ganttproject.core.time.TimeUnitStack;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.base.Strings;
import com.google.common.base.Supplier;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import net.sourceforge.ganttproject.CustomPropertyDefinition;
import net.sourceforge.ganttproject.GPLogger;
import net.sourceforge.ganttproject.language.GanttLanguage;
import net.sourceforge.ganttproject.resource.HumanResource;
import net.sourceforge.ganttproject.resource.HumanResourceManager;
import net.sourceforge.ganttproject.task.Task;
import net.sourceforge.ganttproject.task.TaskManager;
import net.sourceforge.ganttproject.task.TaskProperties;
import net.sourceforge.ganttproject.task.dependency.TaskDependency;
import net.sourceforge.ganttproject.task.dependency.TaskDependencyException;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.SortedMap;
import java.util.logging.Level;
/**
* Class responsible for processing task records in CSV import
*
* @author dbarashev (Dmitry Barashev)
*/
class TaskRecords extends RecordGroup {
static final Comparator<String> OUTLINE_NUMBER_COMPARATOR = new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
try (Scanner sc1 = new Scanner(s1).useDelimiter("\\.");
Scanner sc2 = new Scanner(s2).useDelimiter("\\.")) {
while (sc1.hasNextInt() && sc2.hasNextInt()) {
int diff = sc1.nextInt() - sc2.nextInt();
if (diff != 0) {
return Integer.signum(diff);
}
}
if (sc1.hasNextInt()) {
return 1;
}
if (sc2.hasNextInt()) {
return -1;
}
return 0;
}
}
};
/** List of known (and supported) Task attributes */
enum TaskFields {
ID(TaskDefaultColumn.ID.getNameKey()),
NAME("tableColName"), BEGIN_DATE("tableColBegDate"), END_DATE("tableColEndDate"), WEB_LINK("webLink"),
NOTES("notes"), COMPLETION("tableColCompletion"), RESOURCES("resources"), DURATION("tableColDuration"),
PREDECESSORS(TaskDefaultColumn.PREDECESSORS.getNameKey()), OUTLINE_NUMBER(TaskDefaultColumn.OUTLINE_NUMBER.getNameKey());
private final String text;
TaskFields(final String text) {
this.text = text;
}
@Override
public String toString() {
// Return translated field name
return GanttLanguage.getInstance().getText(text);
}
}
private final Map<Task, String> myAssignmentMap = Maps.newHashMap();
private final Map<Task, String> myPredecessorMap = Maps.newHashMap();
private final SortedMap<String, Task> myWbsMap = Maps.newTreeMap(OUTLINE_NUMBER_COMPARATOR);
private final Map<String, Task> myTaskIdMap = Maps.newHashMap();
private final TaskManager taskManager;
private final HumanResourceManager resourceManager;
private final TimeUnitStack myTimeUnitStack;
TaskRecords(TaskManager taskManager, HumanResourceManager resourceManager, TimeUnitStack timeUnitStack) {
super("Task group",
Sets.newHashSet(GanttCSVOpen.getFieldNames(TaskFields.values())),
Sets.newHashSet(GanttCSVOpen.getFieldNames(TaskFields.NAME, TaskFields.BEGIN_DATE)));
this.taskManager = taskManager;
this.resourceManager = resourceManager;
myTimeUnitStack = timeUnitStack;
}
@Override
public void setHeader(List<String> header) {
super.setHeader(header);
GanttCSVOpen.createCustomProperties(getCustomFields(), taskManager.getCustomPropertyManager());
}
private Date parseDateOrError(String strDate) {
Date result = GanttCSVOpen.language.parseDate(strDate);
if (result == null) {
addError(Level.WARNING, GanttLanguage.getInstance().formatText("impex.csv.error.parse_date",
strDate,
GanttLanguage.getInstance().getShortDateFormat().toPattern(),
GanttLanguage.getInstance().getShortDateFormat().format(new Date())));
}
return result;
}
@Override
protected boolean doProcess(SpreadsheetRecord record) {
if (!super.doProcess(record)) {
return false;
}
if (!hasMandatoryFields(record)) {
return false;
}
Date startDate = parseDateOrError(getOrNull(record, TaskFields.BEGIN_DATE.toString()));
// Create the task
TaskManager.TaskBuilder builder = taskManager.newTaskBuilder()
.withName(getOrNull(record, TaskFields.NAME.toString()))
.withStartDate(startDate)
.withWebLink(getOrNull(record, TaskFields.WEB_LINK.toString()))
.withNotes(getOrNull(record, TaskFields.NOTES.toString()));
if (record.isSet(TaskDefaultColumn.DURATION.getName())) {
builder = builder.withDuration(taskManager.createLength(record.get(TaskDefaultColumn.DURATION.getName())));
}
if (record.isSet(TaskFields.END_DATE.toString())) {
if (record.isSet(TaskDefaultColumn.DURATION.getName())) {
if (Objects.equal(record.get(TaskFields.BEGIN_DATE.toString()), record.get(TaskFields.END_DATE.toString()))
&& "0".equals(record.get(TaskDefaultColumn.DURATION.getName()))) {
builder = builder.withLegacyMilestone();
}
} else {
Date endDate = parseDateOrError(getOrNull(record, TaskFields.END_DATE.toString()));
if (endDate != null) {
builder = builder.withEndDate(myTimeUnitStack.getDefaultTimeUnit().adjustRight(endDate));
}
}
}
if (record.isSet(TaskFields.COMPLETION.toString())) {
String completion = record.get(TaskFields.COMPLETION.toString());
if (!Strings.isNullOrEmpty(completion)) {
builder = builder.withCompletion(Integer.parseInt(completion));
}
}
if (record.isSet(TaskDefaultColumn.COST.getName())) {
try {
String cost = record.get(TaskDefaultColumn.COST.getName());
if (!Strings.isNullOrEmpty(cost)) {
builder = builder.withCost(new BigDecimal(cost));
}
} catch (NumberFormatException e) {
GPLogger.logToLogger(e);
GPLogger.log(String.format("Failed to parse %s as cost value", record.get(TaskDefaultColumn.COST.getName())));
}
}
Task task = builder.build();
if (record.isSet(TaskDefaultColumn.ID.getName())) {
myTaskIdMap.put(record.get(TaskDefaultColumn.ID.getName()), task);
}
myAssignmentMap.put(task, getOrNull(record, TaskFields.RESOURCES.toString()));
myPredecessorMap.put(task, getOrNull(record, TaskDefaultColumn.PREDECESSORS.getName()));
String outlineNumber = getOrNull(record, TaskDefaultColumn.OUTLINE_NUMBER.getName());
if (outlineNumber != null) {
myWbsMap.put(outlineNumber, task);
}
for (String customField : getCustomFields()) {
String value = getOrNull(record, customField);
if (value == null) {
continue;
}
CustomPropertyDefinition def = taskManager.getCustomPropertyManager().getCustomPropertyDefinition(customField);
if (def == null) {
GPLogger.logToLogger("Can't find custom field with name=" + customField + " value=" + value);
continue;
}
task.getCustomValues().addCustomProperty(def, value);
}
return true;
}
@Override
protected void postProcess() {
for (Map.Entry<String, Task> wbsEntry : myWbsMap.entrySet()) {
String outlineNumber = wbsEntry.getKey();
List<String> components = Arrays.asList(outlineNumber.split("\\."));
if (components.size() <= 1) {
continue;
}
String parentOutlineNumber = Joiner.on('.').join(components.subList(0, components.size() - 1));
Task parentTask = myWbsMap.get(parentOutlineNumber);
if (parentTask == null) {
continue;
}
taskManager.getTaskHierarchy().move(wbsEntry.getValue(), parentTask, 0);
}
if (resourceManager != null) {
Map<String, HumanResource> resourceMap = Maps.uniqueIndex(resourceManager.getResources(), new Function<HumanResource, String>() {
@Override
public String apply(HumanResource input) {
return input.getName();
}
});
for (Entry<Task, String> assignment : myAssignmentMap.entrySet()) {
if (assignment.getValue() == null) {
continue;
}
String[] names = assignment.getValue().split(";");
for (String name : names) {
HumanResource resource = resourceMap.get(name);
if (resource != null) {
assignment.getKey().getAssignmentCollection().addAssignment(resource);
}
}
}
}
Function<Integer, Task> taskIndex = new Function<Integer, Task>() {
@Override
public Task apply(Integer id) {
return myTaskIdMap.get(String.valueOf(id));
}
};
for (Entry<Task, String> entry : myPredecessorMap.entrySet()) {
if (entry.getValue() == null) {
continue;
}
Task successor = entry.getKey();
String[] depSpecs = entry.getValue().split(";");
try {
Map<Integer, Supplier<TaskDependency>> constructors = TaskProperties.parseDependencies(
Arrays.asList(depSpecs), successor, taskIndex);
for (Supplier<TaskDependency> constructor : constructors.values()) {
constructor.get();
}
} catch (IllegalArgumentException e) {
GPLogger.logToLogger(String.format("%s\nwhen parsing predecessor specification %s of task %s",
e.getMessage(), entry.getValue(), successor));
} catch (TaskDependencyException e) {
GPLogger.logToLogger(e);
}
}
}
}