diff --git a/navalplanner-gantt-zk/pom.xml b/navalplanner-gantt-zk/pom.xml
index dd6820a9a..e2a167f08 100644
--- a/navalplanner-gantt-zk/pom.xml
+++ b/navalplanner-gantt-zk/pom.xml
@@ -31,6 +31,11 @@
commons-logging
commons-logging
+
+ jgrapht
+ jgrapht
+ 0.7.3
+
diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/Dependency.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/Dependency.java
index 881123873..5a867f6e7 100644
--- a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/Dependency.java
+++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/Dependency.java
@@ -8,6 +8,8 @@ package org.zkoss.ganttz;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
+import org.zkoss.ganttz.util.DependencyBean;
+import org.zkoss.ganttz.util.DependencyType;
import org.zkoss.zk.au.out.AuInvoke;
import org.zkoss.zk.ui.ext.AfterCompose;
import org.zkoss.zul.impl.XulElement;
@@ -21,14 +23,6 @@ public class Dependency extends XulElement implements AfterCompose {
private Task source;
- public Task getSource() {
- return source;
- }
-
- public Task getDestination() {
- return destination;
- }
-
private Task destination;
public Dependency() {
@@ -96,4 +90,18 @@ public class Dependency extends XulElement implements AfterCompose {
public boolean contains(Task task) {
return getSource().equals(task) || getDestination().equals(task);
}
+
+ public Task getSource() {
+ return source;
+ }
+
+ public Task getDestination() {
+ return destination;
+ }
+
+ public DependencyBean getDependencyBean() {
+ return new DependencyBean(source.getTaskBean(), destination
+ .getTaskBean(), DependencyType.END_START);
+ }
+
}
diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/DependencyList.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/DependencyList.java
index 6e76c366f..34aa0aa44 100644
--- a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/DependencyList.java
+++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/DependencyList.java
@@ -42,6 +42,7 @@ public class DependencyList extends XulElement implements AfterCompose {
void addDependency(Dependency dependency) {
appendChild(dependency);
addContextMenu(dependency);
+ publishDependency(dependency);
}
private void addContextMenu(Dependency dependency) {
@@ -83,9 +84,24 @@ public class DependencyList extends XulElement implements AfterCompose {
taskRemovedListener);
}
addContextMenu();
+ publishDependencies();
}
+ private void publishDependencies() {
+ for (Dependency dependency : getDependencies()) {
+ publishDependency(dependency);
+ }
+ }
+
+ private void publishDependency(Dependency dependency) {
+ getPlanner().publishDependency(dependency);
+ }
+
+ private Planner getPlanner() {
+ return getGanttPanel().getPlanner();
+ }
+
private void addContextMenu() {
for (Dependency dependency : getDependencies()) {
addContextMenu(dependency);
@@ -94,8 +110,6 @@ public class DependencyList extends XulElement implements AfterCompose {
private Menupopup contextMenu;
- private Dependency dependencyForContextMenu = null;
-
private Menupopup getContextMenu() {
if (contextMenu == null) {
contextMenu = MenuBuilder.on(getPage(), getDependencies()).item(
diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/ListDetails.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/ListDetails.java
index 3e1d05d44..41048f601 100644
--- a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/ListDetails.java
+++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/ListDetails.java
@@ -33,7 +33,7 @@ public class ListDetails extends HtmlMacroComponent {
taskDetail.setParent(insertionPoint);
taskDetail.afterCompose();
Task task = new Task();
- getPlanner().addTask(task);
+ getPlanner().publishTask(task);
task.setColor("yellow");
task.setId(newId);
}
diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/Planner.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/Planner.java
index 4eaa4dc68..89807f93c 100644
--- a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/Planner.java
+++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/Planner.java
@@ -5,6 +5,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import org.zkoss.ganttz.util.DependencyRegistry;
+import org.zkoss.ganttz.util.TaskBean;
import org.zkoss.zk.ui.ext.AfterCompose;
import org.zkoss.zul.impl.XulElement;
@@ -14,6 +16,8 @@ public class Planner extends XulElement implements AfterCompose {
private Map tasksById = new HashMap();
+ private DependencyRegistry dependencyRegistry = new DependencyRegistry();
+
public Planner() {
}
@@ -42,6 +46,7 @@ public class Planner extends XulElement implements AfterCompose {
throw new IllegalArgumentException("task with id " + taskId
+ " is already in " + tasksById);
tasksById.put(taskId, task);
+ dependencyRegistry.add(task);
}
TaskBean retrieve(String taskId) {
@@ -78,10 +83,21 @@ public class Planner extends XulElement implements AfterCompose {
}
};
taskList.addDependencyListener(dependencyAddedListener);
+ taskList.addTaskRemovedListener(new TaskRemovedListener() {
+ @Override
+ public void taskRemoved(Task taskRemoved) {
+ dependencyRegistry.remove(taskRemoved.getTaskBean());
+ }
+ });
}
- public void addTask(Task task) {
+ public void publishTask(Task task) {
getTaskList().addTask(task);
+ dependencyRegistry.add(task.getTaskBean());
+ }
+
+ public void publishDependency(Dependency dependency) {
+ dependencyRegistry.add(dependency);
}
}
diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/Task.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/Task.java
index 87f0c4525..04643e73d 100755
--- a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/Task.java
+++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/Task.java
@@ -15,6 +15,7 @@ import java.util.ListIterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.zkoss.ganttz.util.TaskBean;
import org.zkoss.lang.Objects;
import org.zkoss.xml.HTMLs;
import org.zkoss.zk.au.AuRequest;
@@ -212,9 +213,9 @@ public class Task extends Div {
this.taskBean.setLengthMilliseconds(getMapper().toMilliseconds(pixels));
}
- void doAddDependency(String dependencyId) {
+ void doAddDependency(String destinyTaskId) {
Dependency dependency = new Dependency(this,
- ((Task) getFellow(dependencyId)));
+ ((Task) getFellow(destinyTaskId)));
fireDependenceAdded(dependency);
}
diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskDetail.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskDetail.java
index 413801f14..6913ebe60 100644
--- a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskDetail.java
+++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskDetail.java
@@ -14,6 +14,7 @@ import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.zkoss.ganttz.util.TaskBean;
import org.zkoss.zk.ui.AbstractComponent;
import org.zkoss.zk.ui.HtmlMacroComponent;
import org.zkoss.zk.ui.ext.AfterCompose;
diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskEditFormComposer.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskEditFormComposer.java
index 2c6c93f21..84df5ec13 100644
--- a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskEditFormComposer.java
+++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskEditFormComposer.java
@@ -3,6 +3,7 @@ package org.zkoss.ganttz;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
+import org.zkoss.ganttz.util.TaskBean;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.util.GenericForwardComposer;
diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/DependencyBean.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/DependencyBean.java
new file mode 100644
index 000000000..7b80951a5
--- /dev/null
+++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/DependencyBean.java
@@ -0,0 +1,113 @@
+package org.zkoss.ganttz.util;
+
+import java.util.Collection;
+import java.util.Date;
+
+/**
+ * This class represents a dependency. Contains the source and the destination.
+ * It also specifies the type of the relationship.
+ * Created at Apr 24, 2009
+ *
+ * @author Óscar González Fernández
+ *
+ */
+public class DependencyBean {
+
+ private enum Calculation {
+ START, END;
+ }
+
+ public static Date calculateStart(TaskBean origin, Date current,
+ Collection extends DependencyBean> dependencies) {
+ return apply(Calculation.START, origin, current, dependencies);
+ }
+
+ public static Date calculateEnd(TaskBean origin, Date current,
+ Collection extends DependencyBean> depencencies) {
+ return apply(Calculation.END, origin, current, depencencies);
+ }
+
+ private static Date apply(Calculation calculation, TaskBean origin,
+ Date current, Collection extends DependencyBean> dependencies) {
+ for (DependencyBean dependency : dependencies) {
+ switch (calculation) {
+ case START:
+ current = dependency.getType().calculateStartDestinyTask(
+ dependency.getSource(), current);
+ break;
+ case END:
+ current = dependency.getType().calculateEndDestinyTask(
+ dependency.getSource(), current);
+ break;
+ default:
+ throw new RuntimeException("unexpected calculation "
+ + calculation);
+ }
+ }
+ return current;
+ }
+
+ private final TaskBean source;
+
+ private final TaskBean destination;
+
+ private final DependencyType type;
+
+ public DependencyBean(TaskBean source, TaskBean destination,
+ DependencyType type) {
+ if (source == null)
+ throw new IllegalArgumentException("source cannot be null");
+ if (destination == null)
+ throw new IllegalArgumentException("destination cannot be null");
+ if (type == null)
+ throw new IllegalArgumentException("type cannot be null");
+ this.source = source;
+ this.destination = destination;
+ this.type = type;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((destination == null) ? 0 : destination.hashCode());
+ result = prime * result + ((source == null) ? 0 : source.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ DependencyBean other = (DependencyBean) obj;
+ if (destination == null) {
+ if (other.destination != null)
+ return false;
+ } else if (!destination.equals(other.destination))
+ return false;
+ if (source == null) {
+ if (other.source != null)
+ return false;
+ } else if (!source.equals(other.source))
+ return false;
+ return true;
+ }
+
+ public TaskBean getSource() {
+ return source;
+ }
+
+ public TaskBean getDestination() {
+ return destination;
+ }
+
+ public DependencyType getType() {
+ return type;
+ }
+
+}
diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/DependencyRegistry.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/DependencyRegistry.java
new file mode 100644
index 000000000..7aadad658
--- /dev/null
+++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/DependencyRegistry.java
@@ -0,0 +1,107 @@
+package org.zkoss.ganttz.util;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.jgrapht.DirectedGraph;
+import org.jgrapht.graph.SimpleDirectedGraph;
+import org.zkoss.ganttz.Dependency;
+
+/**
+ * This class contains a graph with the {@link TaskBean tasks} as vertexes and
+ * the {@link DependencyBean dependency} as arcs. It enforces the rules embodied
+ * in the dependencies and in the duration of the tasks using listeners.
+ * Created at Apr 24, 2009
+ *
+ * @author Óscar González Fernández
+ *
+ */
+public class DependencyRegistry {
+
+ private final DirectedGraph graph = new SimpleDirectedGraph(
+ DependencyBean.class);
+
+ private Map rulesEnforcersByTask = new HashMap();
+
+ private List getOutgoing(TaskBean task) {
+ ArrayList result = new ArrayList();
+ for (DependencyBean dependencyBean : graph.outgoingEdgesOf(task)) {
+ result.add(rulesEnforcersByTask
+ .get(dependencyBean.getDestination()));
+ }
+ return result;
+ }
+
+ private class RulesEnforcer {
+ private final TaskBean task;
+
+ private RulesEnforcer(TaskBean task) {
+ if (task == null)
+ throw new IllegalArgumentException("task cannot be null");
+ this.task = task;
+ this.task.addPropertyChangeListener(new PropertyChangeListener() {
+
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ RulesEnforcer.this.update();
+ updateOutgoing(RulesEnforcer.this.task);
+ }
+ });
+ }
+
+ void update() {
+ Set incoming = graph.incomingEdgesOf(task);
+ Date beginDate = task.getBeginDate();
+ Date newStart = DependencyBean.calculateStart(task, beginDate,
+ incoming);
+ if (!beginDate.equals(newStart))
+ task.setBeginDate(newStart);
+ Date endDate = task.getEndDate();
+ Date newEnd = DependencyBean.calculateEnd(task, endDate, incoming);
+ if (!endDate.equals(newEnd)) {
+ task.setEndDate(newEnd);
+ }
+ }
+ }
+
+ public void add(TaskBean task) {
+ graph.addVertex(task);
+ rulesEnforcersByTask.put(task, new RulesEnforcer(task));
+ }
+
+ public void remove(TaskBean task) {
+ graph.removeVertex(task);
+ rulesEnforcersByTask.remove(task);
+ updateOutgoing(task);
+ }
+
+ private void updateOutgoing(TaskBean task) {
+ for (RulesEnforcer rulesEnforcer : getOutgoing(task)) {
+ rulesEnforcer.update();
+ }
+ }
+
+ public void remove(Dependency dependency) {
+ graph.removeEdge(dependency.getDependencyBean());
+ TaskBean destination = dependency.getDependencyBean().getDestination();
+ rulesEnforcersByTask.get(destination).update();
+ }
+
+ public void add(Dependency dependency) {
+ TaskBean destination = dependency.getDestination().getTaskBean();
+ graph.addEdge(dependency.getSource().getTaskBean(), destination,
+ dependency.getDependencyBean());
+ getEnforcer(destination).update();
+ }
+
+ private RulesEnforcer getEnforcer(TaskBean destination) {
+ return rulesEnforcersByTask.get(destination);
+ }
+
+}
diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/DependencyType.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/DependencyType.java
new file mode 100644
index 000000000..15b4644af
--- /dev/null
+++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/DependencyType.java
@@ -0,0 +1,51 @@
+package org.zkoss.ganttz.util;
+
+import java.util.Date;
+
+/**
+ * This enum tells the type of a depepdency. Each instance contanins the correct
+ * behaviour for that type of dependency .
+ * Created at Apr 24, 2009
+ *
+ * @author Óscar González Fernández
+ *
+ */
+public enum DependencyType {
+
+ VOID {
+ @Override
+ public Date calculateEndDestinyTask(TaskBean originalTask, Date current) {
+ return current;
+ }
+
+ @Override
+ public Date calculateStartDestinyTask(TaskBean originalTask,
+ Date current) {
+ return current;
+ }
+ },
+ END_START {
+ @Override
+ public Date calculateEndDestinyTask(TaskBean originalTask, Date current) {
+ return current;
+ }
+
+ @Override
+ public Date calculateStartDestinyTask(TaskBean originalTask,
+ Date current) {
+ return getBigger(originalTask.getEndDate(), current);
+ }
+ };
+
+ private static Date getBigger(Date date1, Date date2) {
+ if (date1.before(date2))
+ return date2;
+ return date1;
+ }
+
+ public abstract Date calculateEndDestinyTask(TaskBean originTask,
+ Date current);
+
+ public abstract Date calculateStartDestinyTask(TaskBean originTask,
+ Date current);
+}
diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskBean.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/TaskBean.java
similarity index 91%
rename from navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskBean.java
rename to navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/TaskBean.java
index 6b42056ce..08fc065c1 100644
--- a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskBean.java
+++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/TaskBean.java
@@ -1,9 +1,17 @@
-package org.zkoss.ganttz;
+package org.zkoss.ganttz.util;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.Date;
+/**
+ * This class contains the information of a task. It can be modified and
+ * notifies of the changes to the interested parties.
+ * Created at Apr 24, 2009
+ *
+ * @author Óscar González Fernández
+ *
+ */
public class TaskBean {
private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(