/*
* This file is part of LibrePlan
*
* Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
* Desenvolvemento Tecnolóxico de Galicia
* Copyright (C) 2010-2011 Igalia, S.L.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.zkoss.ganttz.data.criticalpath;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.zkoss.ganttz.data.DependencyType;
import org.zkoss.ganttz.data.GanttDate;
import org.zkoss.ganttz.data.IDependency;
import org.zkoss.ganttz.data.constraint.Constraint;
/**
* Class that calculates the critical path of a Gantt diagram graph.
*
* @author Manuel Rego Casasnovas <mrego@igalia.com>
*/
public class CriticalPathCalculator<T, D extends IDependency<T>> {
private final boolean dependenciesConstraintsHavePriority;
public static <T, D extends IDependency<T>> CriticalPathCalculator<T, D> create(
boolean dependenciesConstraintsHavePriority) {
return new CriticalPathCalculator<T, D>(
dependenciesConstraintsHavePriority);
}
private CriticalPathCalculator(boolean dependenciesConstraintsHavePriority) {
this.dependenciesConstraintsHavePriority = dependenciesConstraintsHavePriority;
}
private ICriticalPathCalculable<T> graph;
private LocalDate initDate;
private Map<T, Node<T, D>> nodes;
private InitialNode<T, D> bop;
private LastNode<T, D> eop;
private Map<T, Map<T, DependencyType>> dependencies;
private class VisitorTracker {
private Map<T, Set<T>> visitorsOn = new HashMap<T, Set<T>>();
void visit(T visited, T visitor) {
if (!visitorsOn.containsKey(visited)) {
visitorsOn.put(visited, new HashSet<T>());
}
visitorsOn.get(visited).add(visitor);
}
boolean hasBeenVisitedByAll(T current,
Collection<? extends T> collection) {
return visitorsOn.containsKey(current)
&& visitorsOn.get(current).containsAll(collection);
}
}
public List<T> calculateCriticalPath(ICriticalPathCalculable<T> graph) {
this.graph = graph;
dependencies = new HashMap<T, Map<T, DependencyType>>();
initDate = calculateInitDate();
bop = createBeginningOfProjectNode();
eop = createEndOfProjectNode();
nodes = createGraphNodes();
forward(bop, null, new VisitorTracker());
eop.updateLatestValues();
backward(eop, null, new VisitorTracker());
return getTasksOnCriticalPath();
}
private LocalDate calculateInitDate() {
if (graph.getTasks().isEmpty()) {
return null;
}
GanttDate ganttDate = Collections.min(getStartDates());
return LocalDate.fromDateFields(ganttDate.toDayRoundedDate());
}
private List<GanttDate> getStartDates() {
List<GanttDate> result = new ArrayList<GanttDate>();
for (T task : graph.getTasks()) {
result.add(graph.getStartDate(task));
}
return result;
}
private Collection<T> removeContainers(Collection<T> tasks) {
if (tasks == null) {
return Collections.emptyList();
}
List<T> noConatinersTasks = new ArrayList<T>();
for (T t : tasks) {
if (graph.isContainer(t)) {
List<T> children = graph.getChildren(t);
noConatinersTasks.addAll(removeContainers(children));
} else {
noConatinersTasks.add(t);
}
}
return noConatinersTasks;
}
private InitialNode<T, D> createBeginningOfProjectNode() {
return new InitialNode<T, D>(
removeWithVisibleIncomingDependencies(removeContainers(graph
.getInitialTasks())));
}
private Set<T> removeWithVisibleIncomingDependencies(Collection<T> tasks) {
Set<T> result = new HashSet<T>();
for (T each : tasks) {
if (!graph.hasVisibleIncomingDependencies(each)) {
result.add(each);
}
}
return result;
}
private LastNode<T, D> createEndOfProjectNode() {
return new LastNode<T, D>(
removeWithVisibleOutcomingDependencies(removeContainers(graph
.getLatestTasks())));
}
private Set<T> removeWithVisibleOutcomingDependencies(
Collection<T> removeContainers) {
Set<T> result = new HashSet<T>();
for (T each : removeContainers) {
if (!graph.hasVisibleOutcomingDependencies(each)) {
result.add(each);
}
}
return result;
}
private Map<T, Node<T, D>> createGraphNodes() {
Map<T, Node<T, D>> result = new HashMap<T, Node<T, D>>();
for (T task : graph.getTasks()) {
if (!graph.isContainer(task)) {
Set<T> in = withoutContainers(task, graph
.getIncomingTasksFor(task));
Set<T> out = withoutContainers(task, graph
.getOutgoingTasksFor(task));
Node<T, D> node = new Node<T, D>(task, in, out, graph
.getStartDate(task), graph.getEndDateFor(task));
result.put(task, node);
}
}
for (T task : graph.getTasks()) {
if (graph.isContainer(task)) {
Collection<T> allChildren = removeContainers(Arrays
.asList(task));
Set<T> in = removeChildrenAndParents(task, graph
.getIncomingTasksFor(task));
for (T t : in) {
IDependency<T> dependency = graph
.getDependencyFrom(t, task);
DependencyType type = DependencyType.END_START;
if (dependency != null) {
type = dependency.getType();
}
addDepedenciesAndRelatedTasks(result,
removeContainers(Arrays.asList(t)), allChildren,
type);
}
Set<T> out = removeChildrenAndParents(task, graph
.getOutgoingTasksFor(task));
for (T t : out) {
IDependency<T> dependency = graph
.getDependencyFrom(task, t);
DependencyType type = DependencyType.END_START;
if (dependency != null) {
type = dependency.getType();
}
addDepedenciesAndRelatedTasks(result, allChildren,
removeContainers(Arrays.asList(t)), type);
}
}
}
return result;
}
private void addDepedenciesAndRelatedTasks(Map<T, Node<T, D>> graph,
Collection<T> origins,
Collection<T> destinations, DependencyType type) {
for (T origin : origins) {
for (T destination : destinations) {
graph.get(origin).addNextTask(destination);
graph.get(destination).addPreviousTask(origin);
addDependency(origin, destination, type);
}
}
}
private Set<T> withoutContainers(T task, Set<T> tasks) {
Set<T> result = new HashSet<T>();
for (T t : tasks) {
if (!graph.isContainer(t)) {
result.add(t);
}
}
return result;
}
private Set<T> removeChildrenAndParents(T task, Set<T> tasks) {
Set<T> result = new HashSet<T>();
if (!graph.isContainer(task)) {
return result;
}
for (T t : tasks) {
if (!graph.contains(task, t) && !graph.contains(t, task)) {
result.add(t);
}
}
return result;
}
private void addDependency(T from, T destination, DependencyType type) {
Map<T, DependencyType> destinations = dependencies.get(from);
if (destinations == null) {
destinations = new HashMap<T, DependencyType>();
dependencies.put(from, destinations);
}
destinations.put(destination, type);
}
private DependencyType getDependencyTypeEndStartByDefault(T from, T to) {
if ((from != null) && (to != null)) {
IDependency<T> dependency = graph.getDependencyFrom(from, to);
if (dependency != null) {
return dependency.getType();
} else {
Map<T, DependencyType> destinations = dependencies.get(from);
if (destinations != null) {
DependencyType type = destinations.get(to);
if (type != null) {
return type;
}
}
}
}
return DependencyType.END_START;
}
private void forward(Node<T, D> currentNode, T previousTask,
VisitorTracker visitorTracker) {
T currentTask = currentNode.getTask();
int earliestStart = currentNode.getEarliestStart();
int earliestFinish = currentNode.getEarliestFinish();
Set<T> nextTasks = currentNode.getNextTasks();
if (nextTasks.isEmpty()) {
eop.setEarliestStart(earliestFinish);
} else {
int countStartStart = 0;
for (T task : nextTasks) {
visitorTracker.visit(task, currentTask);
if (graph.isContainer(currentTask)) {
if (graph.contains(currentTask, previousTask)) {
if (graph.contains(currentTask, task)) {
continue;
}
}
}
Node<T, D> node = nodes.get(task);
DependencyType dependencyType = getDependencyTypeEndStartByDefault(
currentTask, task);
Constraint<GanttDate> constraint = getDateConstraints(task);
switch (dependencyType) {
case START_START:
setEarliestStart(node, earliestStart, constraint);
countStartStart++;
break;
case END_END:
setEarliestStart(node, earliestFinish - node.getDuration(),
constraint);
break;
case END_START:
default:
setEarliestStart(node, earliestFinish, constraint);
break;
}
if (visitorTracker.hasBeenVisitedByAll(task,
node.getPreviousTasks())) {
forward(node, currentTask, visitorTracker);
}
}
if (nextTasks.size() == countStartStart) {
eop.setEarliestStart(earliestFinish);
}
}
}
private void setEarliestStart(Node<T, D> node, int earliestStart,
Constraint<GanttDate> constraint) {
if (constraint != null) {
GanttDate date = GanttDate.createFrom(initDate
.plusDays(earliestStart));
date = constraint.applyTo(date);
earliestStart = Days.daysBetween(initDate,
LocalDate.fromDateFields(date.toDayRoundedDate()))
.getDays();
}
node.setEarliestStart(earliestStart);
}
private Constraint<GanttDate> getDateConstraints(T task) {
if (dependenciesConstraintsHavePriority || task == null) {
return null;
}
List<Constraint<GanttDate>> startConstraints = graph
.getStartConstraintsFor(task);
List<Constraint<GanttDate>> endConstraints = graph
.getEndConstraintsFor(task);
if ((startConstraints == null || startConstraints.isEmpty())
&& (endConstraints == null || endConstraints.isEmpty())) {
return null;
}
if (startConstraints == null || startConstraints.isEmpty()) {
return Constraint.coalesce(endConstraints);
}
if (endConstraints == null || endConstraints.isEmpty()) {
return Constraint.coalesce(startConstraints);
}
startConstraints.addAll(endConstraints);
return Constraint.coalesce(startConstraints);
}
private void backward(Node<T, D> currentNode, T nextTask,
VisitorTracker visitorTracker) {
T currentTask = currentNode.getTask();
int latestStart = currentNode.getLatestStart();
int latestFinish = currentNode.getLatestFinish();
Set<T> previousTasks = currentNode.getPreviousTasks();
if (previousTasks.isEmpty()) {
bop.setLatestFinish(latestStart);
} else {
int countEndEnd = 0;
for (T task : previousTasks) {
visitorTracker.visit(task, currentTask);
if (graph.isContainer(currentTask)) {
if (graph.contains(currentTask, nextTask)) {
if (graph.contains(currentTask, task)) {
continue;
}
}
}
Node<T, D> node = nodes.get(task);
DependencyType dependencyType = getDependencyTypeEndStartByDefault(
task, currentTask);
Constraint<GanttDate> constraint = getDateConstraints(task);
switch (dependencyType) {
case START_START:
setLatestFinish(node, latestStart + node.getDuration(),
constraint);
break;
case END_END:
setLatestFinish(node, latestFinish, constraint);
countEndEnd++;
break;
case END_START:
default:
setLatestFinish(node, latestStart, constraint);
break;
}
if (visitorTracker.hasBeenVisitedByAll(task,
node.getNextTasks())) {
backward(node, currentTask, visitorTracker);
}
}
if (previousTasks.size() == countEndEnd) {
bop.setLatestFinish(latestStart);
}
}
}
private void setLatestFinish(Node<T, D> node, int latestFinish,
Constraint<GanttDate> constraint) {
if (constraint != null) {
int duration = node.getDuration();
GanttDate date = GanttDate.createFrom(initDate.plusDays(latestFinish - duration));
date = constraint.applyTo(date);
int daysBetween = Days.daysBetween(initDate,
LocalDate.fromDateFields(date.toDayRoundedDate()))
.getDays();
latestFinish = daysBetween + duration;
}
node.setLatestFinish(latestFinish);
}
private List<T> getTasksOnCriticalPath() {
List<T> result = new ArrayList<T>();
for (Node<T, D> node : nodes.values()) {
if (node.getLatestStart() == node.getEarliestStart()) {
result.add(node.getTask());
}
}
return result;
}
}