/*
GanttProject is an opensource project management tool. License: GPL3
Copyright (C) 2010-2012 Dmitry Barashev, GanttProject Team
This program 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.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package biz.ganttproject.impex.msproject2;
import biz.ganttproject.core.calendar.CalendarEvent;
import biz.ganttproject.core.calendar.GPCalendar.DayType;
import biz.ganttproject.core.calendar.GPCalendarCalc;
import biz.ganttproject.core.calendar.GanttDaysOff;
import biz.ganttproject.core.time.GanttCalendar;
import biz.ganttproject.core.time.TimeDuration;
import net.sf.mpxj.*;
import net.sourceforge.ganttproject.*;
import net.sourceforge.ganttproject.resource.HumanResource;
import net.sourceforge.ganttproject.resource.HumanResourceManager;
import net.sourceforge.ganttproject.task.ResourceAssignment;
import net.sourceforge.ganttproject.task.Task;
import net.sourceforge.ganttproject.task.TaskContainmentHierarchyFacade;
import net.sourceforge.ganttproject.task.TaskManager;
import net.sourceforge.ganttproject.task.dependency.TaskDependency;
import net.sourceforge.ganttproject.task.dependency.TaskDependencySlice;
import javax.swing.*;
import java.math.BigDecimal;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
/**
* Creates MPXJ ProjectFile from GanttProject's IGanttProject.
*
* @author dbarashev (Dmitry Barashev)
*/
class ProjectFileExporter {
private IGanttProject myNativeProject;
private ProjectFile myOutputProject;
public ProjectFileExporter(IGanttProject nativeProject) {
myNativeProject = nativeProject;
myOutputProject = new ProjectFile();
myOutputProject.getProjectConfig().setAutoOutlineLevel(true);
myOutputProject.getProjectConfig().setAutoWBS(true);
myOutputProject.getProjectConfig().setAutoOutlineNumber(true);
myOutputProject.getProjectConfig().setAutoResourceUniqueID(false);
myOutputProject.getProjectConfig().setAutoTaskUniqueID(false);
}
ProjectFile run() throws MPXJException {
Map<Integer, net.sf.mpxj.Task> id2mpxjTask = new HashMap<Integer, net.sf.mpxj.Task>();
exportCalendar();
exportTasks(id2mpxjTask);
exportDependencies(id2mpxjTask);
Map<Integer, Resource> id2mpxjResource = new HashMap<Integer, Resource>();
exportResources(id2mpxjResource);
exportAssignments(id2mpxjTask, id2mpxjResource);
return myOutputProject;
}
private void exportCalendar() {
ProjectCalendar calendar = myOutputProject.addDefaultBaseCalendar();
exportWeekends(calendar);
exportHolidays(calendar);
}
private boolean isWorkingDay(int day) {
return getCalendar().getOnlyShowWeekends() || getCalendar().getWeekDayType(day) == DayType.WORKING;
}
private void exportWeekends(ProjectCalendar calendar) {
ProjectCalendarHours workingDayHours = calendar.getCalendarHours(Day.MONDAY);
calendar.setWorkingDay(Day.MONDAY, isWorkingDay(Calendar.MONDAY));
calendar.setWorkingDay(Day.TUESDAY, isWorkingDay(Calendar.TUESDAY));
calendar.setWorkingDay(Day.WEDNESDAY, isWorkingDay(Calendar.WEDNESDAY));
calendar.setWorkingDay(Day.THURSDAY, isWorkingDay(Calendar.THURSDAY));
calendar.setWorkingDay(Day.FRIDAY, isWorkingDay(Calendar.FRIDAY));
calendar.setWorkingDay(Day.SATURDAY, isWorkingDay(Calendar.SATURDAY));
if (calendar.isWorkingDay(Day.SATURDAY)) {
copyHours(workingDayHours, calendar.addCalendarHours(Day.SATURDAY));
}
calendar.setWorkingDay(Day.SUNDAY, isWorkingDay(Calendar.SUNDAY));
if (calendar.isWorkingDay(Day.SUNDAY)) {
copyHours(workingDayHours, calendar.addCalendarHours(Day.SUNDAY));
}
}
private void copyHours(ProjectCalendarHours from, ProjectCalendarHours to) {
for (DateRange range : from) {
to.addRange(range);
}
}
private void exportHolidays(ProjectCalendar calendar) {
for (CalendarEvent h : getCalendar().getPublicHolidays()) {
if (!h.isRecurring && h.getType() == CalendarEvent.Type.HOLIDAY) {
Date d = h.myDate;
calendar.addCalendarException(d, d);
}
}
}
private void exportTasks(Map<Integer, net.sf.mpxj.Task> id2mpxjTask) throws MPXJException {
// Map<CustomPropertyDefinition, FieldType> customProperty_fieldType = new HashMap<CustomPropertyDefinition, FieldType>();
// collectCustomProperties(getTaskManager().getCustomPropertyManager(), customProperty_fieldType, TaskField.class);
Map<CustomPropertyDefinition, FieldType> customProperty_fieldType = CustomPropertyMapping.buildMapping(getTaskManager());
for (Entry<CustomPropertyDefinition, FieldType> e : customProperty_fieldType.entrySet()) {
myOutputProject.getCustomFields().getCustomField(e.getValue()).setAlias(e.getKey().getName());
}
net.sf.mpxj.Task rootTask = myOutputProject.addTask();
rootTask.setEffortDriven(false);
rootTask.setID(0);
rootTask.setUniqueID(0);
rootTask.setOutlineLevel(0);
rootTask.setWBS("0");
rootTask.setOutlineNumber("0");
rootTask.setStart(convertStartTime(getTaskManager().getProjectStart()));
rootTask.setFinish(convertFinishTime(getTaskManager().getProjectEnd()));
rootTask.setDuration(convertDuration(getTaskManager().createLength(
getTaskManager().getRootTask().getDuration().getTimeUnit(), getTaskManager().getProjectStart(),
getTaskManager().getProjectEnd())));
// rootTask.setDurationFormat(TimeUnit.DAYS);
rootTask.setTaskMode(TaskMode.AUTO_SCHEDULED);
int i = 0;
for (Task t : getTaskHierarchy().getNestedTasks(getTaskHierarchy().getRootTask())) {
exportTask(t, null, 1, ++i, id2mpxjTask, customProperty_fieldType);
}
}
private void exportTask(Task t, net.sf.mpxj.Task mpxjParentTask, int outlineLevel, int ordinalNum,
Map<Integer, net.sf.mpxj.Task> id2mpxjTask, Map<CustomPropertyDefinition, FieldType> customProperty_fieldType) {
final net.sf.mpxj.Task mpxjTask = mpxjParentTask == null ? myOutputProject.addTask() : mpxjParentTask.addTask();
mpxjTask.setOutlineLevel(outlineLevel);
String wbs = (mpxjParentTask == null ? "" : mpxjParentTask.getWBS() + ".") + String.valueOf(ordinalNum);
mpxjTask.setWBS(wbs);
mpxjTask.setOutlineNumber(wbs);
mpxjTask.setUniqueID(convertTaskId(t.getTaskID()));
mpxjTask.setID(id2mpxjTask.size() + 1);
mpxjTask.setName(t.getName());
mpxjTask.setNotes(t.getNotes());
mpxjTask.setMilestone(t.isMilestone());
mpxjTask.setPercentageComplete(t.getCompletionPercentage());
mpxjTask.setHyperlink(((GanttTask) t).getWebLink());
mpxjTask.setIgnoreResourceCalendar(true);
Task[] nestedTasks = getTaskHierarchy().getNestedTasks(t);
mpxjTask.setTaskMode(TaskMode.MANUALLY_SCHEDULED);
Date startTime = convertStartTime(t.getStart().getTime());
mpxjTask.setStart(startTime);
Duration duration = convertDuration(t.getDuration());
mpxjTask.setDuration(duration);
mpxjTask.setManualDuration(duration);
if (t.isMilestone()) {
mpxjTask.setFinish(startTime);
} else {
Date finishTime = convertFinishTime(t.getEnd().getTime());
mpxjTask.setFinish(finishTime);
}
mpxjTask.setCost(t.getCost().getValue());
// mpxjTask.setDurationFormat(TimeUnit.DAYS);
Duration[] durations = getActualAndRemainingDuration(mpxjTask);
mpxjTask.setActualDuration(durations[0]);
mpxjTask.setRemainingDuration(durations[1]);
mpxjTask.setPriority(convertPriority(t));
exportCustomProperties(t.getCustomValues(), customProperty_fieldType, new CustomPropertySetter() {
@Override
public void set(FieldType ft, Object value) {
mpxjTask.set(ft, value);
}
});
id2mpxjTask.put(mpxjTask.getUniqueID(), mpxjTask);
int i = 0;
for (Task child : nestedTasks) {
exportTask(child, mpxjTask, outlineLevel + 1, ++i, id2mpxjTask, customProperty_fieldType);
}
}
private Date convertStartTime(Date gpStartDate) {
Date startTime = myOutputProject.getDefaultCalendar().getStartTime(gpStartDate);
Calendar c = (Calendar) Calendar.getInstance().clone();
c.setTime(gpStartDate);
c.set(Calendar.HOUR, startTime.getHours());
c.set(Calendar.MINUTE, startTime.getMinutes());
return c.getTime();
}
private Date convertFinishTime(Date gpFinishDate) {
Calendar c = (Calendar) Calendar.getInstance().clone();
c.setTime(gpFinishDate);
c.add(Calendar.DAY_OF_YEAR, -1);
Date finishTime = myOutputProject.getDefaultCalendar().getFinishTime(c.getTime());
if (finishTime != null) {
c.set(Calendar.HOUR, finishTime.getHours());
c.set(Calendar.MINUTE, finishTime.getMinutes());
}
return c.getTime();
}
private Duration convertDuration(TimeDuration duration) {
return Duration.getInstance(duration.getLength(), TimeUnit.DAYS);
}
private static Duration[] getActualAndRemainingDuration(net.sf.mpxj.Task mpxjTask) {
return getActualAndRemainingDuration(mpxjTask, 1.0);
}
private static Duration[] getActualAndRemainingDuration(net.sf.mpxj.Task mpxjTask, double load) {
TimeUnit durationUnits = mpxjTask.getDuration().getUnits();
double actualWork = (mpxjTask.getDuration().getDuration() * mpxjTask.getPercentageComplete().doubleValue() * load) / 100;
double remainingWork = mpxjTask.getDuration().getDuration() - actualWork;
return new Duration[] { Duration.getInstance(actualWork, durationUnits),
Duration.getInstance(remainingWork, durationUnits) };
}
private void exportDependencies(Map<Integer, net.sf.mpxj.Task> id2mpxjTask) {
for (Task t : getTaskManager().getTasks()) {
net.sf.mpxj.Task mpxjTask = id2mpxjTask.get(convertTaskId(t.getTaskID()));
TaskDependencySlice dependencies = t.getDependenciesAsDependant();
for (TaskDependency dep : dependencies.toArray()) {
net.sf.mpxj.Task mpxjPredecessor = id2mpxjTask.get(convertTaskId(dep.getDependee().getTaskID()));
assert mpxjPredecessor != null : "Can't find mpxj task for id=" + dep.getDependee().getTaskID();
mpxjTask.addPredecessor(mpxjPredecessor, convertConstraint(dep), convertLag(dep));
}
}
}
private RelationType convertConstraint(TaskDependency dep) {
switch (dep.getConstraint().getType()) {
case finishstart:
return RelationType.FINISH_START;
case startfinish:
return RelationType.START_FINISH;
case finishfinish:
return RelationType.FINISH_FINISH;
case startstart:
return RelationType.START_START;
default:
assert false : "Should not be here";
return null;
}
}
private static Duration convertLag(TaskDependency dep) {
// TODO(dbarashev): Get rid of days
return Duration.getInstance(dep.getDifference(), TimeUnit.DAYS);
}
private Priority convertPriority(Task t) {
switch (t.getPriority()) {
case LOWEST:
return Priority.getInstance(Priority.LOWEST);
case LOW:
return Priority.getInstance(Priority.LOW);
case NORMAL:
return Priority.getInstance(Priority.MEDIUM);
case HIGH:
return Priority.getInstance(Priority.HIGH);
case HIGHEST:
return Priority.getInstance(Priority.HIGHEST);
default:
assert false : "Should not be here";
return Priority.getInstance(Priority.MEDIUM);
}
}
private int convertTaskId(int taskId) {
return taskId == 0 ? getMaxTaskID() + 1 : taskId;
}
private int getMaxTaskID() {
int maxID = 0;
for (Task t : getTaskManager().getTasks()) {
if (t.getTaskID() > maxID) {
maxID = t.getTaskID();
}
}
return maxID;
}
private void exportResources(Map<Integer, Resource> id2mpxjResource) throws MPXJException {
Map<CustomPropertyDefinition, FieldType> customProperty_fieldType = CustomPropertyMapping.buildMapping(getResourceManager());
for (Entry<CustomPropertyDefinition, FieldType> e : customProperty_fieldType.entrySet()) {
myOutputProject.getCustomFields().getCustomField(e.getValue()).setAlias(e.getKey().getName());
}
for (HumanResource hr : getResourceManager().getResources()) {
exportResource(hr, id2mpxjResource, customProperty_fieldType);
}
}
private void exportResource(HumanResource hr, Map<Integer, Resource> id2mpxjResource,
Map<CustomPropertyDefinition, FieldType> customProperty_fieldType) throws MPXJException {
final Resource mpxjResource = myOutputProject.addResource();
mpxjResource.setUniqueID(hr.getId() + 1);
mpxjResource.setID(id2mpxjResource.size() + 1);
mpxjResource.setName(hr.getName());
mpxjResource.setEmailAddress(hr.getMail());
mpxjResource.setType(ResourceType.WORK);
mpxjResource.setCanLevel(false);
if (hr.getStandardPayRate() != BigDecimal.ZERO) {
mpxjResource.setStandardRate(new Rate(hr.getStandardPayRate(), TimeUnit.DAYS));
}
exportDaysOff(hr, mpxjResource);
exportCustomProperties(hr, customProperty_fieldType, new CustomPropertySetter() {
@Override
public void set(FieldType ft, Object value) {
mpxjResource.set(ft, value);
}
});
id2mpxjResource.put(hr.getId(), mpxjResource);
}
private static interface CustomPropertySetter {
void set(FieldType ft, Object value);
}
private static void exportCustomProperties(CustomPropertyHolder holder,
Map<CustomPropertyDefinition, FieldType> customProperty_fieldType, CustomPropertySetter setter) {
for (CustomProperty cp : holder.getCustomProperties()) {
FieldType ft = customProperty_fieldType.get(cp.getDefinition());
if (ft != null) {
setter.set(ft, convertValue(cp));
}
}
}
private static Object convertValue(CustomProperty cp) {
if (cp.getDefinition().getPropertyClass() == CustomPropertyClass.DATE) {
GanttCalendar value = (GanttCalendar) cp.getValue();
return value.getTime();
}
return cp.getValue();
}
private void exportDaysOff(HumanResource hr, Resource mpxjResource) throws MPXJException {
DefaultListModel daysOff = hr.getDaysOff();
if (!daysOff.isEmpty()) {
ProjectCalendar resourceCalendar = mpxjResource.addResourceCalendar();
resourceCalendar.addDefaultCalendarHours();
exportWeekends(resourceCalendar);
resourceCalendar.setParent(myOutputProject.getDefaultCalendar());
// resourceCalendar.setUniqueID(hr.getId());
for (int i = 0; i < daysOff.size(); i++) {
GanttDaysOff dayOff = (GanttDaysOff) daysOff.get(i);
resourceCalendar.addCalendarException(dayOff.getStart().getTime(), dayOff.getFinish().getTime());
}
}
}
private void exportAssignments(Map<Integer, net.sf.mpxj.Task> id2mpxjTask, Map<Integer, Resource> id2mpxjResource) {
for (Task t : getTaskManager().getTasks()) {
net.sf.mpxj.Task mpxjTask = id2mpxjTask.get(convertTaskId(t.getTaskID()));
for (ResourceAssignment ra : t.getAssignments()) {
Resource mpxjResource = id2mpxjResource.get(ra.getResource().getId());
net.sf.mpxj.ResourceAssignment mpxjAssignment = mpxjTask.addResourceAssignment(mpxjResource);
mpxjAssignment.setUnits(ra.getLoad());
mpxjAssignment.setStart(mpxjTask.getStart());
mpxjAssignment.setFinish(mpxjTask.getFinish());
mpxjAssignment.setWork(mpxjTask.getDuration());
Duration[] durations = getActualAndRemainingDuration(mpxjTask, ra.getLoad() / 100.0);
mpxjAssignment.setActualWork(durations[0]);
mpxjAssignment.setRemainingWork(durations[1]);
}
}
}
private TaskManager getTaskManager() {
return myNativeProject.getTaskManager();
}
private TaskContainmentHierarchyFacade getTaskHierarchy() {
return getTaskManager().getTaskHierarchy();
}
private HumanResourceManager getResourceManager() {
return myNativeProject.getHumanResourceManager();
}
private GPCalendarCalc getCalendar() {
return getTaskManager().getCalendar();
}
}