/*
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 net.sourceforge.ganttproject.task.algorithm;
import biz.ganttproject.core.calendar.GPCalendar;
import biz.ganttproject.core.calendar.GPCalendar.DayMask;
import biz.ganttproject.core.calendar.GPCalendarCalc;
import biz.ganttproject.core.time.CalendarFactory;
import biz.ganttproject.core.time.GanttCalendar;
import biz.ganttproject.core.time.TimeDuration;
import biz.ganttproject.core.time.TimeUnit;
import com.google.common.base.Supplier;
import com.google.common.collect.BoundType;
import com.google.common.collect.Lists;
import com.google.common.collect.Range;
import net.sourceforge.ganttproject.GPLogger;
import net.sourceforge.ganttproject.task.Task;
import net.sourceforge.ganttproject.task.TaskContainmentHierarchyFacade;
import net.sourceforge.ganttproject.task.TaskImpl;
import net.sourceforge.ganttproject.task.TaskMutator;
import net.sourceforge.ganttproject.task.algorithm.DependencyGraph.DependencyEdge;
import net.sourceforge.ganttproject.task.algorithm.DependencyGraph.ImplicitSubSuperTaskDependency;
import net.sourceforge.ganttproject.task.algorithm.DependencyGraph.Node;
import net.sourceforge.ganttproject.task.event.TaskDependencyEvent;
import net.sourceforge.ganttproject.task.event.TaskListener;
import net.sourceforge.ganttproject.task.event.TaskListenerAdapter;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.logging.Logger;
/**
* This class walk the dependency graph and updates start and end dates of tasks
* according to information returned by dependency edges.
*
* @author dbarashev
*/
public class SchedulerImpl extends AlgorithmBase {
private final DependencyGraph myGraph;
private boolean isRunning;
private final Supplier<TaskContainmentHierarchyFacade> myTaskHierarchy;
private final TaskListener myTaskListener;
public SchedulerImpl(DependencyGraph graph, Supplier<TaskContainmentHierarchyFacade> taskHierarchy) {
myGraph = graph;
myGraph.addListener(new DependencyGraph.Listener() {
@Override
public void onChange() {
run();
}
});
myTaskHierarchy = taskHierarchy;
myTaskListener = new TaskListenerAdapter() {
@Override
public void dependencyChanged(TaskDependencyEvent e) {
run();
}
};
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
if (isEnabled()) {
run();
}
}
public TaskListener getTaskModelListener() {
return myTaskListener;
}
@Override
public void run() {
if (!isEnabled() || isRunning) {
return;
}
isRunning = true;
try {
doRun();
} finally {
isRunning = false;
}
}
private void doRun() {
int layers = myGraph.checkLayerValidity();
for (int i = 0; i < layers; i++) {
Collection<Node> layer = myGraph.getLayer(i);
for (Node node : layer) {
try {
schedule(node);
} catch (IllegalArgumentException e) {
GPLogger.log(e);
}
}
}
}
private void schedule(Node node) {
Logger logger = GPLogger.getLogger(this);
GPLogger.debug(logger, "Scheduling node %s", node);
Range<Date> startRange = Range.all();
Range<Date> endRange = Range.all();
Range<Date> weakStartRange = Range.all();
Range<Date> weakEndRange = Range.all();
List<Date> subtaskRanges = Lists.newArrayList();
List<DependencyEdge> incoming = node.getIncoming();
GPLogger.debug(logger, ".. #incoming edges=%d", incoming.size());
for (DependencyEdge edge : incoming) {
if (!edge.refresh()) {
continue;
}
if (edge instanceof ImplicitSubSuperTaskDependency) {
subtaskRanges.add(edge.getStartRange().upperEndpoint());
subtaskRanges.add(edge.getEndRange().lowerEndpoint());
} else {
if (edge.isWeak()) {
weakStartRange = weakStartRange.intersection(edge.getStartRange());
weakEndRange = weakEndRange.intersection(edge.getEndRange());
} else {
startRange = startRange.intersection(edge.getStartRange());
endRange = endRange.intersection(edge.getEndRange());
}
}
if (startRange.isEmpty() || endRange.isEmpty()) {
GPLogger.logToLogger("both start and end ranges were calculated as empty for task=" + node.getTask() + ". Skipping it");
}
}
GPLogger.debug(logger, "..Ranges: start=%s end=%s weakStart=%s weakEnd=%s", startRange, endRange, weakStartRange, weakEndRange);
Range<Date> subtasksSpan = subtaskRanges.isEmpty() ?
Range.closed(node.getTask().getStart().getTime(), node.getTask().getEnd().getTime()) : Range.encloseAll(subtaskRanges);
Range<Date> subtreeStartUpwards = subtasksSpan.span(Range.downTo(node.getTask().getStart().getTime(), BoundType.CLOSED));
Range<Date> subtreeEndDownwards = subtasksSpan.span(Range.upTo(node.getTask().getEnd().getTime(), BoundType.CLOSED));
GPLogger.debug(logger, "..Subtasks span=%s", subtasksSpan);
if (!startRange.equals(Range.all())) {
startRange = startRange.intersection(weakStartRange);
} else if (!weakStartRange.equals(Range.all())) {
startRange = weakStartRange.intersection(subtreeStartUpwards);
}
if (!endRange.equals(Range.all())) {
endRange = endRange.intersection(weakEndRange);
} else if (!weakEndRange.equals(Range.all())) {
endRange = weakEndRange.intersection(subtreeEndDownwards);
}
if (node.getTask().getThirdDateConstraint() == TaskImpl.EARLIESTBEGIN && node.getTask().getThird() != null) {
startRange = startRange.intersection(Range.downTo(node.getTask().getThird().getTime(), BoundType.CLOSED));
GPLogger.debug(logger, ".. applying earliest start=%s. Now start range=%s", node.getTask().getThird(), startRange);
}
if (!subtaskRanges.isEmpty()) {
startRange = startRange.intersection(subtasksSpan);
endRange = endRange.intersection(subtasksSpan);
}
GPLogger.debug(logger, ".. finally, start range=%s", startRange);
if (startRange.hasLowerBound()) {
modifyTaskStart(node.getTask(), startRange.lowerEndpoint());
}
if (endRange.hasUpperBound()) {
GPCalendarCalc cal = node.getTask().getManager().getCalendar();
Date endDate = endRange.upperEndpoint();
TimeUnit timeUnit = node.getTask().getDuration().getTimeUnit();
if (DayMask.WORKING == (cal.getDayMask(endDate) & DayMask.WORKING)) {
// in case if calculated end date falls on first day after holidays (say, on Monday)
// we'll want to modify it a little bit, so that it falls on that holidays start
// If we don't do this, it will be done automatically the next time task activities are recalculated,
// and thus task end date will keep changing
Date closestWorkingEndDate = cal.findClosest(
endDate, timeUnit, GPCalendarCalc.MoveDirection.BACKWARD, GPCalendar.DayType.WORKING);
Date closestNonWorkingEndDate = cal.findClosest(
endDate, timeUnit, GPCalendarCalc.MoveDirection.BACKWARD, GPCalendar.DayType.NON_WORKING, closestWorkingEndDate);
// If there is a non-working date between current task end and closest working date
// then we're really just after holidays
if (closestNonWorkingEndDate != null && closestWorkingEndDate.before(closestNonWorkingEndDate)) {
// we need to adjust-right closest working date to position to the very beginning of the holidays interval
Date nonWorkingPeriodStart = timeUnit.adjustRight(closestWorkingEndDate);
if (nonWorkingPeriodStart.after(node.getTask().getStart().getTime())) {
endDate = nonWorkingPeriodStart;
}
}
}
modifyTaskEnd(node.getTask(), endDate);
}
}
private void modifyTaskEnd(Task task, Date newEnd) {
if (task.getEnd().getTime().equals(newEnd)) {
return;
}
GanttCalendar newEndCalendar = CalendarFactory.createGanttCalendar(newEnd);
if (getDiagnostic() != null) {
getDiagnostic().addModifiedTask(task, null, newEnd);
}
TaskMutator mutator = task.createMutator();
mutator.setEnd(newEndCalendar);
mutator.commit();
}
private void modifyTaskStart(Task task, Date newStart) {
if (task.getStart().getTime().equals(newStart)) {
return;
}
GanttCalendar newStartCalendar = CalendarFactory.createGanttCalendar(newStart);
if (getDiagnostic() != null) {
getDiagnostic().addModifiedTask(task, newStart, null);
}
TaskMutator mutator = task.createMutator();
if (myTaskHierarchy.get().hasNestedTasks(task)) {
mutator.setStart(newStartCalendar);
mutator.commit();
} else {
TimeDuration shift = task.getManager().createLength(task.getDuration().getTimeUnit(), task.getStart().getTime(), newStart);
mutator.shift(shift);
mutator.commit();
}
}
}