/* GanttProject is an opensource project management tool. Copyright (C) 2011 GanttProject Team This program 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. 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package net.sourceforge.ganttproject.task.dependency; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import net.sourceforge.ganttproject.GPLogger; import net.sourceforge.ganttproject.task.Task; import net.sourceforge.ganttproject.task.TaskContainmentHierarchyFacade; import net.sourceforge.ganttproject.task.dependency.TaskDependency.Hardness; import net.sourceforge.ganttproject.task.dependency.constraint.FinishFinishConstraintImpl; import net.sourceforge.ganttproject.task.dependency.constraint.FinishStartConstraintImpl; /** * Created by IntelliJ IDEA. User: bard Date: 14.02.2004 Time: 16:02:48 To * change this template use File | Settings | File Templates. */ public class TaskDependencyCollectionImpl implements TaskDependencyCollection { private Set<TaskDependency> myDependencies = new HashSet<TaskDependency>(); private SortedMap<SearchKey, TaskDependency> mySearchKey2dependency = new TreeMap<SearchKey, TaskDependency>(); private final EventDispatcher myEventDispatcher; private final TaskContainmentHierarchyFacade.Factory myTaskHierarchyFactory; public TaskDependencyCollectionImpl(TaskContainmentHierarchyFacade.Factory taskHierarchyFactory, EventDispatcher myEventDispatcher) { this.myEventDispatcher = myEventDispatcher; myTaskHierarchyFactory = taskHierarchyFactory; } @Override public TaskDependency[] getDependencies() { return myDependencies.toArray(new TaskDependency[0]); } @Override public TaskDependency[] getDependencies(Task task) { SearchKey fromKey = new RangeSearchFromKey(task); SearchKey toKey = new RangeSearchToKey(task); SortedMap<SearchKey, TaskDependency> submap = mySearchKey2dependency.subMap(fromKey, toKey); return submap.values().toArray(new TaskDependency[0]); } @Override public TaskDependency[] getDependenciesAsDependant(Task dependant) { SearchKey fromKey = new SearchKey(SearchKey.DEPENDANT, dependant.getTaskID(), -1); SearchKey toKey = new SearchKey(SearchKey.DEPENDEE, dependant.getTaskID(), -1); SortedMap<SearchKey, TaskDependency> submap = mySearchKey2dependency.subMap(fromKey, toKey); return submap.values().toArray(new TaskDependency[0]); } @Override public TaskDependency[] getDependenciesAsDependee(Task dependee) { SearchKey fromKey = new SearchKey(SearchKey.DEPENDEE, dependee.getTaskID(), -1); SearchKey toKey = new SearchKey(Integer.MAX_VALUE, dependee.getTaskID(), -1); SortedMap<SearchKey, TaskDependency> submap = mySearchKey2dependency.subMap(fromKey, toKey); return submap.values().toArray(new TaskDependency[0]); } @Override public TaskDependency createDependency(Task dependant, Task dependee) throws TaskDependencyException { return createDependency(dependant, dependee, new FinishStartConstraintImpl()); } @Override public TaskDependency createDependency(Task dependant, Task dependee, TaskDependencyConstraint constraint) throws TaskDependencyException { return createDependency(dependant, dependee, constraint, getDefaultHardness()); } protected TaskDependency.Hardness getDefaultHardness() { return TaskDependency.Hardness.STRONG; } @Override public TaskDependency createDependency(Task dependant, Task dependee, TaskDependencyConstraint constraint, Hardness hardness) throws TaskDependencyException { TaskDependency result = auxCreateDependency(dependant, dependee, constraint, hardness); addDependency(result); return result; } @Override public boolean canCreateDependency(Task dependant, Task dependee) { if (dependant == dependee) { return false; } if (false == getTaskHierarchy().areUnrelated(dependant, dependee)) { return false; } SearchKey key = new SearchKey(SearchKey.DEPENDANT, dependant.getTaskID(), dependee.getTaskID()); if (mySearchKey2dependency.containsKey(key)) { return false; } TaskDependency testDep = new TaskDependencyImpl(dependant, dependee, this); if (isLooping(testDep)) { return false; } return true; } @Override public void deleteDependency(TaskDependency dependency) { delete(dependency); } void fireChanged(TaskDependency dependency) { myEventDispatcher.fireDependencyChanged(dependency); } @Override public void clear() { doClear(); } @Override public TaskDependencyCollectionMutator createMutator() { return new MutatorImpl(); } private class MutatorImpl implements TaskDependencyCollectionMutator { private Map<TaskDependency, MutationInfo> myQueue = new LinkedHashMap<TaskDependency, MutationInfo>(); private MutationInfo myCleanupMutation; @Override public void commit() { List<MutationInfo> mutations = new ArrayList<MutationInfo>(myQueue.values()); if (myCleanupMutation != null) { mutations.add(myCleanupMutation); } Collections.sort(mutations); for (int i = 0; i < mutations.size(); i++) { MutationInfo next = mutations.get(i); switch (next.myOperation) { case MutationInfo.ADD: { try { addDependency(next.myDependency); } catch (TaskDependencyException e) { if (!GPLogger.log(e)) { e.printStackTrace(System.err); } } break; } case MutationInfo.DELETE: { delete(next.myDependency); break; } case MutationInfo.CLEAR: { doClear(); break; } } } } @Override public void clear() { myQueue.clear(); myCleanupMutation = new MutationInfo(null, MutationInfo.CLEAR); } @Override public TaskDependency createDependency(Task dependant, Task dependee) throws TaskDependencyException { return createDependency(dependant, dependee, new FinishFinishConstraintImpl()); } @Override public TaskDependency createDependency(Task dependant, Task dependee, TaskDependencyConstraint constraint) throws TaskDependencyException { return createDependency(dependant, dependee, constraint, TaskDependency.Hardness.STRONG); } @Override public TaskDependency createDependency(Task dependant, Task dependee, TaskDependencyConstraint constraint, Hardness hardness) throws TaskDependencyException { TaskDependency result = auxCreateDependency(dependant, dependee, constraint, hardness); myQueue.put(result, new MutationInfo(result, MutationInfo.ADD)); return result; } @Override public void deleteDependency(TaskDependency dependency) { MutationInfo info = myQueue.get(dependency); if (info == null) { myQueue.put(dependency, new MutationInfo(dependency, MutationInfo.DELETE)); } else if (info.myOperation == MutationInfo.ADD) { myQueue.remove(dependency); } } } private static class MutationInfo implements Comparable<MutationInfo> { static final int ADD = 0; static final int DELETE = 1; static final int CLEAR = 2; final TaskDependency myDependency; final int myOperation; final int myOrder = ourOrder++; static int ourOrder; public MutationInfo(TaskDependency myDependency, int myOperation) { this.myDependency = myDependency; this.myOperation = myOperation; } @Override public int compareTo(MutationInfo rvalue) { return myOrder - rvalue.myOrder; } } private TaskDependency auxCreateDependency(Task dependant, Task dependee, TaskDependencyConstraint constraint, Hardness hardness) { TaskDependency result = new TaskDependencyImpl(dependant, dependee, this, constraint, hardness, 0); return result; } void addDependency(TaskDependency dep) throws TaskDependencyException { if (myDependencies.contains(dep)) { throw new TaskDependencyException("Dependency=" + dep + " already exists"); } if (this.isLooping(dep)) { throw new TaskDependencyException("Dependency=" + dep + " is looping"); } if (false == getTaskHierarchy().areUnrelated(dep.getDependant(), dep.getDependee())) { throw new TaskDependencyException("In dependency=" + dep + " one of participants is a supertask of another"); } myDependencies.add(dep); // mySearchKey2dependency.put(new SearchKey(SearchKey.DEPENDANT, (TaskDependencyImpl) dep), dep); mySearchKey2dependency.put(new SearchKey(SearchKey.DEPENDEE, (TaskDependencyImpl) dep), dep); myEventDispatcher.fireDependencyAdded(dep); } boolean isLooping(TaskDependency dep) { LoopDetector detector = new LoopDetector(dep.getDependant().getManager()); return detector.isLooping(dep); } boolean _isLooping(TaskDependency dep) { Set<Task> tasksInvolved = new HashSet<Task>(); tasksInvolved.add(dep.getDependee()); return _isLooping(dep, tasksInvolved); } private boolean _isLooping(TaskDependency dep, Set<Task> tasksInvolved) { Task dependant = dep.getDependant(); if (tasksInvolved.contains(dependant)) { return true; } for (Iterator<Task> tasks = tasksInvolved.iterator(); tasks.hasNext();) { Task nextInvolved = tasks.next(); if (false == getTaskHierarchy().areUnrelated(nextInvolved, dependant)) { return true; } } tasksInvolved.add(dependant); { TaskDependency[] nextDeps = dependant.getDependenciesAsDependee().toArray(); for (int i = 0; i < nextDeps.length; i++) { if (_isLooping(nextDeps[i], tasksInvolved)) { return true; } } } Task[] nestedTasks = getTaskHierarchy().getNestedTasks(dependant); for (int i = 0; i < nestedTasks.length; i++) { tasksInvolved.add(nestedTasks[i]); TaskDependency[] nextDeps = nestedTasks[i].getDependenciesAsDependee().toArray(); for (int j = 0; j < nextDeps.length; j++) { if (_isLooping(nextDeps[j], tasksInvolved)) { return true; } } } tasksInvolved.remove(dependant); return false; } void delete(TaskDependency dep) { myDependencies.remove(dep); SearchKey key1 = new SearchKey(SearchKey.DEPENDANT, dep.getDependant().getTaskID(), dep.getDependee().getTaskID()); SearchKey key2 = new SearchKey(SearchKey.DEPENDEE, dep.getDependee().getTaskID(), dep.getDependant().getTaskID()); mySearchKey2dependency.remove(key1); mySearchKey2dependency.remove(key2); myEventDispatcher.fireDependencyRemoved(dep); // SearchKey fromKey = new RangeSearchFromKey(dep.getDependant()); // SearchKey toKey = new RangeSearchToKey(dep.getDependant()); // mySearchKey2dependency.subMap(fromKey, toKey).clear(); // fromKey = new RangeSearchFromKey(dep.getDependee()); // toKey = new RangeSearchToKey(dep.getDependee()); // mySearchKey2dependency.subMap(fromKey, toKey).clear(); } public void doClear() { myDependencies.clear(); mySearchKey2dependency.clear(); } protected TaskContainmentHierarchyFacade getTaskHierarchy() { return myTaskHierarchyFactory.createFacade(); } }