diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/montecarlo/MonteCarloCriticalPathBuilder.java b/libreplan-webapp/src/main/java/org/libreplan/web/montecarlo/MonteCarloCriticalPathBuilder.java
new file mode 100644
index 000000000..0243f0955
--- /dev/null
+++ b/libreplan-webapp/src/main/java/org/libreplan/web/montecarlo/MonteCarloCriticalPathBuilder.java
@@ -0,0 +1,241 @@
+/*
+ * 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-2012 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 .
+ */
+
+package org.libreplan.web.montecarlo;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.libreplan.business.planner.entities.Dependency;
+import org.libreplan.business.planner.entities.Task;
+import org.libreplan.business.planner.entities.TaskElement;
+import org.libreplan.business.planner.entities.TaskGroup;
+
+/**
+ *
+ * @author Diego Pino García
+ *
+ * Constructs all the possible critical paths, departing from a list of
+ * elements that contain all the tasks which are in the critical path.
+ * The algorithm determines first all the possible starting tasks and
+ * navigates them forward until reaching an end.
+ *
+ * Navigating from a {@link Task} to a {@link TaskGroup} is a bit
+ * troublesome. The algorithm considers than when there is a link
+ * between a task and a taskgroup, the task is connected with all the
+ * taskgroup's children which have: a) no incoming dependencies b) has
+ * no incoming dependencies from a task that it's not a children of that
+ * taskgroup.
+ *
+ * Why the list of tasks in the critical path is not the only one
+ * critical path? It could be the case some of the tasks in that list
+ * finish at the same time (in parallel for instance). In those cases
+ * there are many critical paths and it's what this classes solves.
+ */
+public class MonteCarloCriticalPathBuilder {
+
+ private List tasksInCriticalPath;
+
+ private MonteCarloCriticalPathBuilder(List tasksInCriticalPath) {
+ this.tasksInCriticalPath = tasksInCriticalPath;
+ }
+
+ public static MonteCarloCriticalPathBuilder create(
+ List tasksInCriticalPath) {
+ return new MonteCarloCriticalPathBuilder(tasksInCriticalPath);
+ }
+
+ /**
+ * Constructs all possible paths starting from those tasks in the critical
+ * path have no incoming dependencies or have incoming dependencies to other
+ * tasks not in the critical path.
+ *
+ * Once all possible path were constructed, filter only those paths which
+ * all their tasks are in the list of tasks in the critical path
+ *
+ * @param tasksInCriticalPath
+ * @return
+ */
+ public List> buildAllPossibleCriticalPaths() {
+ List> result = new ArrayList>();
+ if (tasksInCriticalPath.size() == 1) {
+ result.add(tasksInCriticalPath);
+ return result;
+ }
+ List> allPaths = new ArrayList>();
+ for (Task each : getStartingTasks(tasksInCriticalPath)) {
+ allPaths.addAll(allPossiblePaths(each));
+ }
+ for (List path : allPaths) {
+ if (isCriticalPath(path)) {
+ result.add(path);
+ }
+ }
+ return result;
+ }
+
+ private Collection> allPossiblePaths(Task task) {
+ Collection> result = new ArrayList>();
+ List path = Collections.singletonList(task);
+ allPossiblePaths(path, result);
+ return result;
+ }
+
+ private void allPossiblePaths(List path,
+ Collection> allPaths) {
+ TaskElement lastTask = getLastTask(path);
+ List destinations = getDestinations(lastTask);
+ if (!destinations.isEmpty()) {
+ for (Task each : destinations) {
+ allPossiblePaths(newPath(path, each), allPaths);
+ }
+ } else {
+ allPaths.add(path);
+ }
+ }
+
+ private TaskElement getLastTask(List path) {
+ return path.get(path.size() - 1);
+ }
+
+ private List newPath(List path, Task task) {
+ List result = new ArrayList();
+ result.addAll(path);
+ result.add(task);
+ return result;
+ }
+
+ private List getDestinations(TaskElement task) {
+ Set result = new HashSet();
+ Set dependencies = getOutgoingDependencies(task);
+ TaskGroup parent = task.getParent();
+ if (parent != null) {
+ if (parent.getEndDate().equals(task.getEndDate())) {
+ result.addAll(getDestinations((task.getParent())));
+ }
+ }
+ for (Dependency each : dependencies) {
+ TaskElement destination = each.getDestination();
+ if (isTask(destination)) {
+ result.add((Task) destination);
+ }
+ if (isTaskGroup(destination)) {
+ result.addAll(taskGroupChildren((TaskGroup) destination));
+ }
+ }
+ return new ArrayList(result);
+ }
+
+ private Set taskGroupChildren(TaskGroup taskGroup) {
+ Set result = new HashSet();
+ for (TaskElement child : taskGroup.getChildren()) {
+ if (isTask(child) && isStartingTask((Task) child)) {
+ result.add((Task) child);
+ }
+ if (isTaskGroup(child)) {
+ result.addAll(taskGroupChildren((TaskGroup) child));
+ }
+ }
+ return result;
+ }
+
+ /**
+ * A startingTask in a TaskGroup is a task that:
+ * a) Has no incoming dependencies
+ * b) All their incoming dependencies are from tasks in a different group
+ *
+ * @param task
+ * @return
+ */
+ private boolean isStartingTask(Task task) {
+ Set dependencies = getIncomingDependencies(task);
+ return dependencies.isEmpty()
+ && !parents(origins(dependencies)).contains(task.getParent());
+ }
+
+ private List parents(List taskElements) {
+ List result = new ArrayList();
+ for (TaskElement each: taskElements) {
+ result.add(each.getParent());
+ }
+ return result;
+ }
+
+ private List origins(Set dependencies) {
+ List result = new ArrayList();
+ for (Dependency each: dependencies) {
+ result.add(each.getOrigin());
+ }
+ return result;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Set getIncomingDependencies(TaskElement taskElement) {
+ return taskElement != null ? taskElement
+ .getDependenciesWithThisDestination() : Collections.EMPTY_SET;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Set getOutgoingDependencies(TaskElement taskElement) {
+ return (taskElement != null) ? taskElement
+ .getDependenciesWithThisOrigin() : Collections.EMPTY_SET;
+ }
+
+ private boolean isTaskGroup(TaskElement taskElement) {
+ return taskElement instanceof TaskGroup;
+ }
+
+ private boolean isTask(TaskElement taskElement) {
+ return taskElement instanceof Task;
+ }
+
+ private List getStartingTasks(List tasks) {
+ List result = new ArrayList();
+ for (Task each : tasks) {
+ if (isStartingTask(each) && noneParentHasIncomingDependencies(each)) {
+ result.add(each);
+ }
+ }
+ return result;
+ }
+
+ private boolean noneParentHasIncomingDependencies(Task each) {
+ TaskGroup parent = each.getParent();
+ while (parent != null && getIncomingDependencies(parent).isEmpty()) {
+ parent = parent.getParent();
+ }
+ return (parent == null);
+ }
+
+ private boolean isCriticalPath(List path) {
+ for (Task each : path) {
+ if (!tasksInCriticalPath.contains(each)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
\ No newline at end of file
diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/montecarlo/MonteCarloModel.java b/libreplan-webapp/src/main/java/org/libreplan/web/montecarlo/MonteCarloModel.java
index f245ca01a..6fbc6fd56 100644
--- a/libreplan-webapp/src/main/java/org/libreplan/web/montecarlo/MonteCarloModel.java
+++ b/libreplan-webapp/src/main/java/org/libreplan/web/montecarlo/MonteCarloModel.java
@@ -26,7 +26,6 @@ import static org.libreplan.web.I18nHelper._;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
@@ -46,7 +45,6 @@ import org.libreplan.business.planner.daos.ITaskElementDAO;
import org.libreplan.business.planner.entities.Dependency;
import org.libreplan.business.planner.entities.Task;
import org.libreplan.business.planner.entities.TaskElement;
-import org.libreplan.business.planner.entities.TaskGroup;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
@@ -61,18 +59,6 @@ import org.zkoss.ganttz.util.LongOperationFeedback.IDesktopUpdatesEmitter;
* list of tasks represents a critical path. There could be many
* critical paths in scheduling.
*
- * A big chunk of code goes for determining all the possible critical
- * paths, departing from a list of elements that contains all the tasks
- * which are in the critical path. The algorithm determines first all
- * the possible starting tasks and navigates them forward until reaching
- * an end.
- *
- * Navigating from a task to a taskgroup is a bit cumbersome. The
- * algorithm considers than when there is a link between a task a
- * taskgroup, the task is connected with all the taskgroup's children
- * which have: a) no incoming dependencies b) has no incoming
- * dependencies from a task that it's not a children of that taskgroup.
- *
*/
@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
@@ -123,6 +109,13 @@ public class MonteCarloModel implements IMonteCarloModel {
}
}
+ private List> buildAllPossibleCriticalPaths(
+ List tasksInCriticalPath) {
+ MonteCarloCriticalPathBuilder criticalPathBuilder = MonteCarloCriticalPathBuilder
+ .create(tasksInCriticalPath);
+ return criticalPathBuilder.buildAllPossibleCriticalPaths();
+ }
+
/**
* Calculating all the critical paths, may need to explore other tasks that
* are not part of the tasks that are on the critical path. So it's
@@ -188,219 +181,6 @@ public class MonteCarloModel implements IMonteCarloModel {
return result;
}
- /**
- * Constructs all possible paths starting from those tasks in the critical
- * path have no incoming dependencies or have incoming dependencies to other
- * tasks not in the critical path.
- *
- * Once all possible path were constructed, filter only those paths which
- * all their tasks are in the list of tasks in the critical path
- *
- * @param tasksInCriticalPath
- * @return
- */
- private List> buildAllPossibleCriticalPaths(
- List tasksInCriticalPath) {
-
- List> result = new ArrayList>();
- List> allPaths = new ArrayList>();
-
- for (Task each : getStartingTasks(tasksInCriticalPath)) {
- buildAllPossiblePaths(each, new ArrayList(), allPaths);
- }
- for (List path : allPaths) {
- if (isCriticalPath(path)) {
- result.add(path);
- }
- }
- return result;
- }
-
- /**
- * Returns the list of starting tasks
- *
- * A task is a starting task if a) has no incoming dependencies b) the only
- * incoming dependencies that it has are to other tasks which are no tasks in
- * the critical path
- *
- * @param tasks
- * @return
- */
- private List getStartingTasks(List tasks) {
- List result = new ArrayList();
- for (Task each : tasks) {
- List origins = getOriginsFrom(each);
- if (onlyTasksInCriticalPath(origins).isEmpty()) {
- result.add(each);
- }
- }
- return result;
- }
-
- private List getOriginsFrom(Task task) {
- List result = new ArrayList(), tasks;
-
- tasks = getOriginsFrom(task.getDependenciesWithThisDestination());
- if (!tasks.isEmpty()) {
- result.addAll(tasks);
- } else {
- tasks = getOriginsFrom(task.getParent()
- .getDependenciesWithThisDestination());
- if (!tasks.isEmpty()) {
- result.addAll(tasks);
- }
- }
- return result;
- }
-
- private List getOriginsFrom(Set dependencies) {
- List result = new ArrayList();
- for (Dependency each : dependencies) {
- TaskElement taskElement = each.getOrigin();
- if (taskElement instanceof TaskGroup) {
- final TaskGroup taskGroup = (TaskGroup) taskElement;
- List children = onlyTasksInCriticalPath(taskGroup
- .getChildren());
- result.addAll(toTaskIfNecessary(children));
- }
- if (taskElement instanceof Task) {
- result.add((Task) taskElement);
- }
- }
- return result;
- }
-
- private List onlyTasksInCriticalPath(
- List extends TaskElement> tasks) {
- return onlyTasksInGroup(tasks, tasksInCriticalPath);
- }
-
- private List onlyTasksInGroup(
- List extends TaskElement> tasks, List extends TaskElement> group) {
- List result = new ArrayList();
- for (TaskElement each : tasks) {
- if (inTaskList(each, group)) {
- result.add(each);
- }
- }
- return result;
- }
-
- private boolean inTaskList(TaskElement task, List extends TaskElement> tasks) {
- for (TaskElement each : tasks) {
- if (isSameTask(task, each)) {
- return true;
- }
- }
- return false;
- }
-
- private boolean isSameTask(TaskElement task1, TaskElement task2) {
- return task1.getOrderElement().getCode()
- .equals(task2.getOrderElement().getCode());
- }
-
- private boolean isCriticalPath(List path) {
- for (Task each : path) {
- if (!inTaskList(each, tasksInCriticalPath)) {
- return false;
- }
- }
- return true;
- }
-
- private void buildAllPossiblePaths(Task task, List path,
- List> allPaths) {
- List destinations = getDestinationsFrom(task);
- if (destinations.size() == 0) {
- path.add(task);
- allPaths.add(path);
- return;
- }
- for (Task each : destinations) {
- List oldPath = copyPath(path);
- path.add(task);
- buildAllPossiblePaths((Task) each, path, allPaths);
- path = oldPath;
- }
- }
-
- private List copyPath(List path) {
- List result = new ArrayList();
- for (Task each : path) {
- result.add(each);
- }
- return result;
- }
-
- private List getDestinationsFrom(Task task) {
- List result = new ArrayList(), tasks;
-
- tasks = getDestinationsFrom(task.getDependenciesWithThisOrigin());
- if (!tasks.isEmpty()) {
- result.addAll(tasks);
- } else {
- tasks = getDestinationsFrom(task.getParent()
- .getDependenciesWithThisOrigin());
- if (!tasks.isEmpty()) {
- result.addAll(tasks);
- }
- }
- return result;
- }
-
- private List getDestinationsFrom(Set dependencies) {
- List result = new ArrayList();
- for (Dependency each : dependencies) {
- TaskElement taskElement = each.getDestination();
- if (taskElement instanceof TaskGroup) {
- final TaskGroup taskGroup = (TaskGroup) taskElement;
- List tasks = onlyTasksWithIncomingDependenciesToOtherTasksNotInThisGroup(taskGroup
- .getChildren());
- result.addAll(toTaskIfNecessary(tasks));
- }
- if (taskElement instanceof Task) {
- result.add((Task) taskElement);
- }
- }
- return result;
- }
-
- private List onlyTasksWithIncomingDependenciesToOtherTasksNotInThisGroup(
- List tasks) {
- List result = new ArrayList();
- for (TaskElement each : tasks) {
- Set dependencies = each
- .getDependenciesWithThisDestination();
- if (dependencies.isEmpty()) {
- result.add(each);
- continue;
- }
- for (Dependency dependency : dependencies) {
- TaskElement origin = dependency.getOrigin();
- if (origin instanceof Task && !inTaskList(origin, tasks)) {
- result.add(origin);
- }
- }
- }
- return result;
- }
-
- private Collection extends Task> toTaskIfNecessary(
- List taskElements) {
- List result = new ArrayList();
- for (TaskElement each : taskElements) {
- if (each instanceof TaskGroup) {
- final TaskGroup taskGroup = (TaskGroup) each;
- result.addAll(toTaskIfNecessary(taskGroup.getChildren()));
- }
- if (each instanceof Task) {
- result.add((Task) each);
- }
- }
- return result;
- }
-
private List toMonteCarloTaskList(List path) {
List result = new ArrayList();
for (Task each : path) {