/*
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 com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;
import com.google.common.collect.BoundType;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Range;
import com.google.common.collect.Sets;
import com.google.common.collect.TreeMultimap;
import net.sourceforge.ganttproject.GPLogger;
import net.sourceforge.ganttproject.task.Task;
import net.sourceforge.ganttproject.task.TaskContainmentHierarchyFacade;
import net.sourceforge.ganttproject.task.dependency.TaskDependency;
import net.sourceforge.ganttproject.task.dependency.TaskDependency.Hardness;
import net.sourceforge.ganttproject.task.dependency.TaskDependencyConstraint;
import net.sourceforge.ganttproject.task.dependency.TaskDependencyException;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Set;
/**
* A graph of dependencies between tasks which is used for scheduling algorithm.
* In this graph nodes are tasks, and edges are either explicit or implicit dependencies between tasks.
*
* Graph is topologically ordered, and each node knows its level. Adding or removing dependencies or moving tasks
* in the task hierarchy may change node levels.
*
* @author dbarashev
*/
public class DependencyGraph {
public static interface Listener {
void onChange();
}
public static interface Logger {
void logDependencyLoop(String title, String message);
}
public static final Logger THROWING_LOGGER = new Logger() {
@Override
public void logDependencyLoop(String title, String message) {
throw new TaskDependencyException(message);
}
};
/**
* Dependency defines a constraint on its target task start and end dates. Constraints
* are normally either points or semi-open intervals on the date axis.
*/
public static interface DependencyEdge {
/**
* @return dst node start date constraint
*/
Range<Date> getStartRange();
/**
* @return dst node end date constraint
*/
Range<Date> getEndRange();
/**
* @return this dependency target node
*/
Node getDst();
/**
* @return this dependency source node
*/
Node getSrc();
/**
* refreshes constraint information
*/
boolean refresh();
boolean isWeak();
}
/**
* Explicit dependency is constructed from {@link TaskDependency} instances and corresponds
* to dependencies explicitly created by a user
*/
static class ExplicitDependencyImpl implements DependencyEdge {
private final TaskDependency myDep;
private Range<Date> myStartRange;
private Range<Date> myEndRange;
private final Node mySrcNode;
private final Node myDstNode;
private boolean isWeak = false;
ExplicitDependencyImpl(TaskDependency dep, Node srcNode, Node dstNode) {
myDep = dep;
mySrcNode = srcNode;
myDstNode = dstNode;
}
@Override
public Range<Date> getStartRange() {
return myStartRange;
}
@Override
public Range<Date> getEndRange() {
return myEndRange;
}
@Override
public boolean refresh() {
GPCalendarCalc calendar = myDstNode.myTask.getManager().getCalendar();
TaskDependencyConstraint.Collision nextCollision = myDep.getConstraint().getCollision();
Date acceptableStart = nextCollision.getAcceptableStart().getTime();
isWeak = !nextCollision.isActive() && myDep.getHardness() == Hardness.RUBBER;
switch (nextCollision.getVariation()) {
case TaskDependencyConstraint.Collision.START_EARLIER_VARIATION:
if (0 == (calendar.getDayMask(acceptableStart) & DayMask.WORKING)) {
acceptableStart = calendar.findClosest(acceptableStart, myDstNode.myTask.getDuration().getTimeUnit(),
GPCalendarCalc.MoveDirection.BACKWARD, GPCalendar.DayType.WORKING);
}
myStartRange = Range.upTo(acceptableStart, BoundType.CLOSED);
break;
case TaskDependencyConstraint.Collision.START_LATER_VARIATION:
if (0 == (calendar.getDayMask(acceptableStart) & DayMask.WORKING)) {
acceptableStart = calendar.findClosest(acceptableStart, myDstNode.myTask.getDuration().getTimeUnit(),
GPCalendarCalc.MoveDirection.FORWARD, GPCalendar.DayType.WORKING);
}
myStartRange = Range.downTo(acceptableStart, BoundType.CLOSED);
break;
case TaskDependencyConstraint.Collision.NO_VARIATION:
myStartRange = Range.singleton(acceptableStart);
break;
}
myEndRange = Range.all();
return true;
}
@Override
public boolean isWeak() {
return isWeak;
}
@Override
public Node getDst() {
return myDstNode;
}
@Override
public Node getSrc() {
return mySrcNode;
}
@Override
public int hashCode() {
return myDep.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ExplicitDependencyImpl == false) {
return false;
}
ExplicitDependencyImpl that = (ExplicitDependencyImpl) obj;
return this.myDep.equals(that.myDep);
}
@Override
public String toString() {
return myDep.toString();
}
}
/**
* Implicit dependency between a subtask and a supertask. It puts constraints
* on supertask bounds: it should start not later than subtask starts and
* should start not earlier than subtask ends
*/
static class ImplicitSubSuperTaskDependency implements DependencyEdge {
private final Node mySubTask;
private final Node mySuperTask;
private Range<Date> myStartRange;
private Range<Date> myEndRange;
ImplicitSubSuperTaskDependency(Node subTask, Node superTask) {
mySubTask = subTask;
mySuperTask = superTask;
}
@Override
public Range<Date> getStartRange() {
return myStartRange;
}
@Override
public Range<Date> getEndRange() {
return myEndRange;
}
@Override
public Node getDst() {
return mySuperTask;
}
@Override
public Node getSrc() {
return mySubTask;
}
@Override
public boolean refresh() {
myStartRange = Range.upTo(mySubTask.myTask.getStart().getTime(), BoundType.CLOSED);
myEndRange = Range.downTo(mySubTask.myTask.getEnd().getTime(), BoundType.CLOSED);
return true;
}
@Override
public int hashCode() {
return Objects.hashCode(mySubTask, mySuperTask);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ImplicitSubSuperTaskDependency == false) {
return false;
}
ImplicitSubSuperTaskDependency that = (ImplicitSubSuperTaskDependency) obj;
return this.mySubTask.myTask.equals(that.mySubTask.myTask) && this.mySuperTask.myTask.equals(that.mySuperTask.myTask);
}
@Override
public boolean isWeak() {
return false;
}
@Override
public String toString() {
return mySubTask.toString() + " is a subtask of " + mySuperTask.toString();
}
}
/**
* Implicit dependency which is inherited by subtasks when explicit dependency to their supertask
* is added to the graph.
*/
static class ImplicitInheritedDependency implements DependencyEdge {
private final DependencyEdge myExplicitDep;
private final Node mySrc;
private final Node myDst;
private ImplicitInheritedDependency(DependencyEdge explicitIncoming, Node supertaskNode, Node subtaskNode) {
assert isAncestor(explicitIncoming.getDst(), supertaskNode);
myExplicitDep = explicitIncoming;
mySrc = explicitIncoming.getSrc();
myDst = subtaskNode;
}
@Override
public Range<Date> getStartRange() {
return myExplicitDep.getStartRange();
}
@Override
public Range<Date> getEndRange() {
return myExplicitDep.getEndRange();
}
@Override
public Node getDst() {
return myDst;
}
@Override
public Node getSrc() {
return mySrc;
}
@Override
public boolean refresh() {
return myExplicitDep.refresh();
}
@Override
public int hashCode() {
return Objects.hashCode(mySrc.myTask, myDst.myTask);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ImplicitInheritedDependency == false) {
return false;
}
ImplicitInheritedDependency that = (ImplicitInheritedDependency) obj;
return this.mySrc.myTask.equals(that.mySrc.myTask) && this.myDst.myTask.equals(that.myDst.myTask);
}
@Override
public boolean isWeak() {
return true;
}
@Override
public String toString() {
return "Dependency inherited from supertask:" + myExplicitDep.toString();
}
}
private static class Transaction {
private boolean isRunning;
private final Set<Node> myTouchedNodes = Sets.newHashSet();
boolean isRunning() {
return isRunning;
}
void touch(Node node) {
myTouchedNodes.add(node);
}
void start() {
isRunning = true;
}
void rollback() {
for (Node node : myTouchedNodes) {
node.revertData();
}
isRunning = false;
}
}
private static class NodeData {
private int myLevel = 0;
private final List<DependencyEdge> myIncoming;
private final List<DependencyEdge> myOutgoing;
private final Node myNode;
private final Transaction myTxn;
private final NodeData myBackup;
NodeData(Node node, Transaction txn) {
myNode = node;
myTxn = txn;
myIncoming = Lists.newArrayList();
myOutgoing = Lists.newArrayList();
myBackup = null;
}
private NodeData(NodeData backup) {
myNode = backup.myNode;
myTxn = backup.myTxn;
myLevel = backup.myLevel;
myIncoming = Lists.newArrayList(backup.myIncoming);
myOutgoing = Lists.newArrayList(backup.myOutgoing);
myBackup = backup;
myTxn.touch(myNode);
}
NodeData revert() {
return (myBackup == null) ? this : myBackup;
}
List<DependencyEdge> getIncoming() {
return Collections.unmodifiableList(myIncoming);
}
List<DependencyEdge> getOutgoing() {
return Collections.unmodifiableList(myOutgoing);
}
int getLevel() {
return myLevel;
}
NodeData setLevel(int level) {
if (!myTxn.isRunning() || myBackup != null) {
myLevel = level;
return this;
}
return new NodeData(this).setLevel(level);
}
NodeData addOutgoing(DependencyEdge dep) {
if (!myTxn.isRunning() || myBackup != null) {
myOutgoing.add(dep);
return this;
}
return new NodeData(this).addOutgoing(dep);
}
NodeData addIncoming(DependencyEdge dep) {
if (!myTxn.isRunning() || myBackup != null) {
myIncoming.add(dep);
return this;
}
return new NodeData(this).addIncoming(dep);
}
NodeData removeOutgoing(DependencyEdge edge) {
if (!myTxn.isRunning() || myBackup != null) {
myOutgoing.remove(edge);
return this;
}
return new NodeData(this).removeOutgoing(edge);
}
NodeData removeIncoming(DependencyEdge edge) {
if (!myTxn.isRunning() || myBackup != null) {
myIncoming.remove(edge);
return this;
}
return new NodeData(this).removeIncoming(edge);
}
}
public static class Node {
private final Task myTask;
private NodeData myData;
Node(Task task, Transaction txn) {
assert task != null;
myTask = task;
myData = new NodeData(this, txn);
}
public void revertData() {
myData = myData.revert();
}
boolean promoteLayer(GraphData data) {
int maxLevel = -1;
for (DependencyEdge edge : myData.getIncoming()) {
maxLevel = Math.max(maxLevel, edge.getSrc().getLevel());
}
if (maxLevel + 1 == myData.getLevel()) {
return false;
}
data.removeFromLevel(myData.getLevel(), this);
myData = myData.setLevel(maxLevel + 1);
data.addToLevel(myData.getLevel(), this);
return true;
}
boolean demoteLayer(GraphData data) {
int maxLevel = -1;
for (DependencyEdge edge : myData.getIncoming()) {
maxLevel = Math.max(maxLevel, edge.getSrc().getLevel());
}
if (maxLevel + 1 == myData.getLevel()) {
return false;
}
assert maxLevel + 1 < myData.getLevel();
data.removeFromLevel(myData.getLevel(), this);
myData = myData.setLevel(maxLevel + 1);
data.addToLevel(myData.getLevel(), this);
return true;
}
public int getLevel() {
return myData.getLevel();
}
public List<DependencyEdge> getOutgoing() {
return myData.getOutgoing();
}
public List<DependencyEdge> getIncoming() {
return myData.getIncoming();
}
void addOutgoing(DependencyEdge dep) {
myData = myData.addOutgoing(dep);
}
void addIncoming(DependencyEdge dep) {
myData = myData.addIncoming(dep);
}
void removeOutgoing(DependencyEdge edge) {
myData = myData.removeOutgoing(edge);
}
void removeIncoming(DependencyEdge edge) {
myData = myData.removeIncoming(edge);
}
public Task getTask() {
return myTask;
}
@Override
public String toString() {
return myTask.toString();
}
}
private static class GraphData {
private static Multimap<Integer, Node> createEmptyLayers() {
return TreeMultimap.<Integer, Node>create(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
}, new Comparator<Node>() {
@Override
public int compare(Node o1, Node o2) {
return o1.myTask.getTaskID() - o2.myTask.getTaskID();
}
});
}
private final Multimap<Integer, Node> myLayers = createEmptyLayers();
private final GraphData myBackup;
private final Transaction myTxn;
public GraphData(GraphData backup, Transaction txn) {
myBackup = backup;
myTxn = txn;
}
public GraphData(Transaction txn) {
this(null, txn);
}
GraphData withTransaction() {
if (!myTxn.isRunning() || myBackup != null) {
return this;
}
return new GraphData(this, myTxn);
}
void addToLevel(int level, Node node) {
myLayers.put(level, node);
}
void removeFromLevel(int level, Node node) {
if (!myTxn.isRunning()) {
myLayers.remove(level, node);
} else {
myLayers.put(-level - 1, node);
}
}
Collection<Node> getLayer(int num) {
Collection<Node> result = myLayers.get(num);
if (!myTxn.isRunning() || myBackup == null) {
return Collections.unmodifiableCollection(result);
}
if (myLayers.get(num).size() + myLayers.get(-num - 1).size() == 0) {
return Collections.unmodifiableCollection(result);
}
result = Sets.newLinkedHashSet(myBackup.getLayer(num));
result.addAll(myLayers.get(num));
result.removeAll(myLayers.get(-num - 1));
return result;
}
int checkLayerValidity() {
int prev = -1;
Multimap<Integer, Node> layers;
if (!myTxn.isRunning() || myBackup == null) {
layers = myLayers;
} else {
layers = createEmptyLayers();
int maxBackupLayerNum = 0;
for (Integer num : myBackup.myLayers.keySet()) {
Collection<Node> layer = myBackup.myLayers.get(num);
if (myLayers.get(num).size() + myLayers.get(-num - 1).size() > 0) {
layer = Sets.newLinkedHashSet(layer);
layer.addAll(myLayers.get(num));
layer.removeAll(myLayers.get(-num - 1));
}
if (!layer.isEmpty()) {
layers.putAll(num, layer);
}
maxBackupLayerNum = Math.max(maxBackupLayerNum, num);
}
for (Integer num : myLayers.keySet()) {
if (num > maxBackupLayerNum) {
layers.putAll(num, myLayers.get(num));
}
}
}
for (Integer num : layers.keySet()) {
Preconditions.checkState(num == prev + 1, "It appears that there is a dependency loop. Task layers are:\n%s", myLayers);
prev = num;
}
return layers.keySet().size();
}
GraphData rollback() {
return (!myTxn.isRunning() || myBackup == null) ? this : myBackup.rollback();
}
}
private final Map<Task, Node> myNodeMap = Maps.newHashMap();
private final Supplier<TaskContainmentHierarchyFacade> myTaskHierarchy;
private final List<Listener> myListeners = Lists.newArrayList();
private Logger myLogger;
private final Transaction myTxn = new Transaction();
private GraphData myData = new GraphData(myTxn);
public DependencyGraph(Supplier<TaskContainmentHierarchyFacade> taskHierarchy) {
this(taskHierarchy, new Logger() {
@Override
public void logDependencyLoop(String title, String message) {
GPLogger.log(title + "\n" + message);
}
});
}
public DependencyGraph(Supplier<TaskContainmentHierarchyFacade> taskHierarchy, Logger logger) {
myTaskHierarchy = taskHierarchy;
myLogger = logger;
}
/**
* Adds a task to the graph. It is expected that task which is added is a top-level task
* with no subtasks
*
* @param t task being added
*/
public void addTask(Task t) {
//assert t.getDependencies().toArray().length == 0 : "Task has deps:" + t.getDependencies().toArray();
//assert myTaskHierarchy.get().hasNestedTasks(t) == false : "Task has nested tasks: " + myTaskHierarchy.get().getNestedTasks(t);
Node node = new Node(t, myTxn);
myData.withTransaction().addToLevel(0, node);
myNodeMap.put(t, node);
fireGraphChanged();
}
/**
* Removes task from the graph with its incoming and outgoing edge
* @param task task to remove
*/
public void removeTask(Task task) {
Node node = myNodeMap.get(task);
if (node == null) {
return;
}
for (DependencyEdge edge : Lists.newArrayList(node.getOutgoing())) {
removeEdge(edge);
}
for (DependencyEdge edge : Lists.newArrayList(node.getIncoming())) {
removeEdge(edge);
}
fireGraphChanged();
}
Node getNode(Task t) {
return myNodeMap.get(t);
}
/**
* Adds an explicit dependency. If dependency target is a node with
* incoming sub-super task edges, recursively adds implicit inherited
* dependencies to the subtree
*
* @param dep dependency to add
*/
public void addDependency(TaskDependency dep) {
Task srcTask = dep.getDependee();
Node srcNode = myNodeMap.get(srcTask);
if (srcNode == null) {
return;
}
Node dstNode = myNodeMap.get(dep.getDependant());
if (dstNode == null) {
return;
}
DependencyEdge edge = new ExplicitDependencyImpl(dep, srcNode, dstNode);
addEdge(edge);
addInheritedDependencies(edge, dstNode);
fireGraphChanged();
}
private void addInheritedDependencies(DependencyEdge edge, Node root) {
Deque<Node> subtree = Lists.newLinkedList();
subtree.add(root);
while (!subtree.isEmpty()) {
root = subtree.pollFirst();
for (DependencyEdge incoming : root.getIncoming()) {
if (incoming instanceof ImplicitSubSuperTaskDependency) {
assert myTaskHierarchy.get().getContainer(incoming.getSrc().myTask) == root.myTask;
ImplicitInheritedDependency implicitIncoming = new ImplicitInheritedDependency(edge, edge.getDst(), incoming.getSrc());
addEdge(implicitIncoming);
subtree.add(incoming.getSrc());
}
}
}
}
private void addEdge(DependencyEdge edge) {
edge.getSrc().addOutgoing(edge);
edge.getDst().addIncoming(edge);
PriorityQueue<Node> queue = new PriorityQueue<DependencyGraph.Node>(11, new Comparator<Node>() {
@Override
public int compare(Node o1, Node o2) {
return o1.getLevel() - o2.getLevel();
}
});
queue.add(edge.getDst());
Map<Task, DependencyEdge> queuedTasks = Maps.newHashMap();
Map<Task, DependencyEdge> pastTasks = Maps.newHashMap();
pastTasks.put(edge.getSrc().getTask(), null);
queuedTasks.put(edge.getDst().getTask(), edge);
while (!queue.isEmpty()) {
Node node = queue.poll();
pastTasks.put(node.getTask(), queuedTasks.remove(node.getTask()));
if (node.promoteLayer(myData = myData.withTransaction())) {
for (DependencyEdge outEdge : node.getOutgoing()) {
if (!queuedTasks.containsKey(outEdge.getDst().getTask())) {
if (pastTasks.containsKey(outEdge.getDst().getTask())) {
myLogger.logDependencyLoop("Dependency loop detected", buildLoop(pastTasks, outEdge) + "\n\nLast dependency has been ignored");
continue;
}
queue.add(outEdge.getDst());
queuedTasks.put(outEdge.getDst().getTask(), outEdge);
}
}
}
}
}
private static String buildLoop(Map<Task, DependencyEdge> pastTasks, DependencyEdge closingEdge) {
Set<DependencyEdge> visitedEdges = Sets.newHashSet();
List<String> trace = Lists.newArrayList();
trace.add(closingEdge.toString());
for (DependencyEdge prevEdge = pastTasks.get(closingEdge.getSrc().getTask());
prevEdge != null; prevEdge = pastTasks.get(prevEdge.getSrc().getTask())) {
if (visitedEdges.contains(prevEdge)) {
break;
}
visitedEdges.add(prevEdge);
trace.add(prevEdge.toString());
}
Collections.reverse(trace);
return Joiner.on("<br>").join(trace);
}
/**
* Removes explicit dependency. Also removes all inherited dependencies constructed from that one
*
* @param dep dependency to remove
*/
public void removeDependency(TaskDependency dep) {
Node srcNode = myNodeMap.get(dep.getDependee());
Node dstNode = myNodeMap.get(dep.getDependant());
if (srcNode == null && dstNode == null) {
return;
}
assert (srcNode != null && dstNode != null) : "Inconsistent dependency graph state: for dep=" + dep + " one of the ends is missing";
DependencyEdge diedEdge = findExplicitDependency(dep, srcNode, dstNode);
if (diedEdge == null) {
return;
}
removeEdge(diedEdge);
for (DependencyEdge edge : Lists.newArrayList(srcNode.getOutgoing())) {
if (edge instanceof ImplicitInheritedDependency) {
if (((ImplicitInheritedDependency)edge).myExplicitDep == diedEdge) {
removeEdge(edge);
}
}
}
fireGraphChanged();
}
private DependencyEdge findExplicitDependency(TaskDependency dep, Node srcNode, Node dstNode) {
for (DependencyEdge edge : srcNode.getOutgoing()) {
if (edge.getDst() == dstNode && edge instanceof ExplicitDependencyImpl) {
if (((ExplicitDependencyImpl)edge).myDep == dep) {
return edge;
}
}
}
return null;
}
private void removeEdge(DependencyEdge edge) {
edge.getSrc().removeOutgoing(edge);
edge.getDst().removeIncoming(edge);
Deque<DependencyEdge> queue = new LinkedList<DependencyEdge>();
queue.add(edge);
while (!queue.isEmpty()) {
edge = queue.pollFirst();
if (edge.getDst().demoteLayer(myData = myData.withTransaction())) {
queue.addAll(edge.getDst().getOutgoing());
}
}
}
/**
* Reflects moving tasks in the task hierarchy. In graph task move consists of removing
* implicit inherited dependencies from the whole subtree being moved and adding new dependencies
* in the destination.
*
* @param what task being moved
* @param where new container or {@code null} if task is moved to the top level
*/
public void move(Task what, Task where) {
Node subNode = myNodeMap.get(what);
if (subNode == null) {
return;
}
boolean removedAny = removeImplicitDependencies(subNode);
Node superNode = myNodeMap.get(where);
if (superNode == null) {
if (removedAny) {
fireGraphChanged();
}
return;
}
for (DependencyEdge incomingEdge : Lists.newArrayList(superNode.getIncoming())) {
if (incomingEdge instanceof ImplicitSubSuperTaskDependency == false) {
if (incomingEdge instanceof ImplicitInheritedDependency) {
incomingEdge = ((ImplicitInheritedDependency)incomingEdge).myExplicitDep;
}
ImplicitInheritedDependency implicitIncoming = new ImplicitInheritedDependency(incomingEdge, superNode, subNode);
addEdge(implicitIncoming);
addInheritedDependencies(incomingEdge, subNode);
}
}
addEdge(new ImplicitSubSuperTaskDependency(subNode, superNode));
fireGraphChanged();
}
private boolean removeImplicitDependencies(final Node root) {
boolean removed = false;
Deque<Node> queue = Lists.newLinkedList();
queue.add(root);
for (DependencyEdge outgoing : Lists.newArrayList(root.getOutgoing())) {
if (outgoing instanceof ImplicitSubSuperTaskDependency) {
removed = true;
removeEdge(outgoing);
}
}
while (!queue.isEmpty()) {
Node node = queue.pollFirst();
for (DependencyEdge incoming : Lists.newArrayList(node.getIncoming())) {
if (incoming instanceof ImplicitInheritedDependency) {
if (((ImplicitInheritedDependency)incoming).myExplicitDep.getDst() != root) {
removed = true;
removeEdge(incoming);
}
}
if (incoming instanceof ImplicitSubSuperTaskDependency) {
queue.add(incoming.getSrc());
}
}
}
return removed;
}
int checkLayerValidity() {
return myData.checkLayerValidity();
}
public Collection<Node> getLayer(int num) {
return myData.getLayer(num);
}
public void addListener(Listener l) {
myListeners.add(l);
}
private void fireGraphChanged() {
if (myTxn.isRunning()) {
return;
}
for (Listener l : myListeners) {
l.onChange();
}
}
public void clear() {
myData = myData.rollback();
myData.myLayers.clear();
myNodeMap.clear();
}
public void startTransaction() {
if (myTxn.isRunning()) {
return;
}
myTxn.start();
}
public void rollbackTransaction() {
if (!myTxn.isRunning()) {
return;
}
myData = myData.rollback();
myTxn.rollback();
}
public void setLogger(Logger logger) {
myLogger = logger;
}
public Logger getLogger() {
return myLogger;
}
private static boolean isAncestor(Node ancestor, Node descendant) {
TaskContainmentHierarchyFacade taskHierarchy = descendant.getTask().getManager().getTaskHierarchy();
for (Task parent = descendant.getTask(); parent != null; parent = taskHierarchy.getContainer(parent)) {
if (parent == ancestor.getTask()) {
return true;
}
}
return false;
}
}