001: /*
002: The contents of this file are subject to the Common Public Attribution License
003: Version 1.0 (the "License"); you may not use this file except in compliance with
004: the License. You may obtain a copy of the License at
005: http://www.projity.com/license . The License is based on the Mozilla Public
006: License Version 1.1 but Sections 14 and 15 have been added to cover use of
007: software over a computer network and provide for limited attribution for the
008: Original Developer. In addition, Exhibit A has been modified to be consistent
009: with Exhibit B.
010:
011: Software distributed under the License is distributed on an "AS IS" basis,
012: WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
013: specific language governing rights and limitations under the License. The
014: Original Code is OpenProj. The Original Developer is the Initial Developer and
015: is Projity, Inc. All portions of the code written by Projity are Copyright (c)
016: 2006, 2007. All Rights Reserved. Contributors Projity, Inc.
017:
018: Alternatively, the contents of this file may be used under the terms of the
019: Projity End-User License Agreeement (the Projity License), in which case the
020: provisions of the Projity License are applicable instead of those above. If you
021: wish to allow use of your version of this file only under the terms of the
022: Projity License and not to allow others to use your version of this file under
023: the CPAL, indicate your decision by deleting the provisions above and replace
024: them with the notice and other provisions required by the Projity License. If
025: you do not delete the provisions above, a recipient may use your version of this
026: file under either the CPAL or the Projity License.
027:
028: [NOTE: The text of this license may differ slightly from the text of the notices
029: in Exhibits A and B of the license at http://www.projity.com/license. You should
030: use the latest text at http://www.projity.com/license for your modifications.
031: You may not remove this license text from the source files.]
032:
033: Attribution Information: Attribution Copyright Notice: Copyright � 2006, 2007
034: Projity, Inc. Attribution Phrase (not exceeding 10 words): Powered by OpenProj,
035: an open source solution from Projity. Attribution URL: http://www.projity.com
036: Graphic Image as provided in the Covered Code as file: openproj_logo.png with
037: alternatives listed on http://www.projity.com/logo
038:
039: Display of Attribution Information is required in Larger Works which are defined
040: in the CPAL as a work which combines Covered Code or portions thereof with code
041: not governed by the terms of the CPAL. However, in addition to the other notice
042: obligations, all copies of the Covered Code in Executable and Source Code form
043: distributed must, as a form of attribution of the original author, include on
044: each user interface screen the "OpenProj" logo visible to all users. The
045: OpenProj logo should be located horizontally aligned with the menu bar and left
046: justified on the top left of the screen adjacent to the File menu. The logo
047: must be at least 100 x 25 pixels. When users click on the "OpenProj" logo it
048: must direct them back to http://www.projity.com.
049: */
050: package com.projity.pm.dependency;
051:
052: import java.text.MessageFormat;
053: import java.util.ArrayList;
054: import java.util.Collection;
055: import java.util.Iterator;
056: import java.util.List;
057:
058: import javax.swing.SwingUtilities;
059: import javax.swing.undo.UndoableEdit;
060: import javax.swing.undo.UndoableEditSupport;
061:
062: import org.apache.commons.collections.Predicate;
063:
064: import com.projity.association.InvalidAssociationException;
065: import com.projity.pm.task.SubProj;
066: import com.projity.pm.task.Task;
067: import com.projity.strings.Messages;
068: import com.projity.undo.DataFactoryUndoController;
069: import com.projity.undo.DependencyCreationEdit;
070: import com.projity.undo.DependencyDeletionEdit;
071: import com.projity.undo.DependencySetFieldsEdit;
072: import com.projity.util.Alert;
073:
074: /**
075: * Manages the creation and deleting of dependencies as well as events
076: */
077: public class DependencyService {
078: private static DependencyService instance = null;
079:
080: public static DependencyService getInstance() {
081: if (instance == null)
082: instance = new DependencyService();
083: return instance;
084: }
085:
086: public Dependency newDependency(HasDependencies predecessor,
087: HasDependencies successor, int dependencyType, long lead,
088: Object eventSource) throws InvalidAssociationException {
089: if (predecessor == successor)
090: throw new InvalidAssociationException(Messages
091: .getString("Message.cantLinkToSelf"));
092: Task successorTask = (Task) successor;
093: if (successorTask.isExternal())
094: throw new InvalidAssociationException(Messages
095: .getString("Message.cantLinkToExternal"));
096: if (successorTask.isSubproject()
097: && !((SubProj) successorTask).isWritable())
098: throw new InvalidAssociationException(Messages
099: .getString("Message.cantLinkToClosedSubproject"));
100:
101: Dependency dependency = Dependency.getInstance(predecessor,
102: successor, dependencyType, lead);
103: dependency.testValid(false); // throws if exception
104: connect(dependency, eventSource);
105: return dependency;
106: }
107:
108: //for deserialization
109: public void initDependency(Dependency dependency,
110: HasDependencies predecessor, HasDependencies successor,
111: Object eventSource) throws InvalidAssociationException {
112: dependency.setPredecessor(predecessor);
113: dependency.setSuccessor(successor);
114: if (!dependency.isDisabled()) // allow for calling a second time once invalidated
115: dependency.testValid(false); // throws if exception
116: connect(dependency, eventSource);
117: }
118:
119: public void addStartSentinelDependency(HasDependencies sentinel,
120: HasDependencies successor) {
121: Dependency dependency = Dependency.getInstance(sentinel,
122: successor, DependencyType.SS, 0);
123: sentinel.getSuccessorList().add(dependency);
124: // System.out.println("adding start sentinel dependency task is " + successor);
125: }
126:
127: public void addEndSentinelDependency(HasDependencies sentinel,
128: HasDependencies predecessor) {
129: Dependency dependency = Dependency.getInstance(predecessor,
130: sentinel, DependencyType.FS, 0);
131: sentinel.getPredecessorList().add(dependency);
132: // System.out.println("adding end sentinel dependency task is " + predecessor);
133: }
134:
135: public boolean removeEndSentinel(HasDependencies sentinel,
136: HasDependencies task) {
137: Dependency dependency;
138: dependency = (Dependency) sentinel.getPredecessorList()
139: .findLeft(task);
140: if (dependency != null) {
141: sentinel.getPredecessorList().remove(dependency);
142: return true;
143: // System.out.println("removing end sentinel dependency task is " + dependency.getPredecessor());
144: }
145: return false;
146: }
147:
148: public boolean removeStartSentinel(HasDependencies sentinel,
149: HasDependencies task) {
150: Dependency dependency;
151: dependency = (Dependency) sentinel.getSuccessorList()
152: .findRight(task);
153: if (dependency != null) {
154: sentinel.getSuccessorList().remove(dependency);
155: return true;
156: // System.out.println("removing start sentinel dependency task is " + dependency.getSuccessor());
157: }
158: return false;
159: }
160:
161: public void connect(Dependency dependency, Object eventSource) {
162: dependency.getPredecessor().getSuccessorList().add(dependency);
163: dependency.getSuccessor().getPredecessorList().add(dependency);
164: updateSentinels(dependency);
165: if (eventSource != null) {
166: dependency.fireCreateEvent(eventSource);
167: }
168:
169: UndoableEditSupport undoableEditSupport = getUndoableEditSupport(dependency);
170: if (undoableEditSupport != null && eventSource != null
171: && !(eventSource instanceof UndoableEdit)) {
172: undoableEditSupport.postEdit(new DependencyCreationEdit(
173: dependency, eventSource));
174: }
175: }
176:
177: public void fireTaskPredecessors(Collection list) {
178: Iterator i = list.iterator();
179: while (i.hasNext()) {
180: Iterator j = ((Task) i.next()).getPredecessorList()
181: .iterator();
182: while (j.hasNext())
183: ((Dependency) j.next()).fireCreateEvent(this );
184: }
185: }
186:
187: public void remove(Dependency dependency, Object eventSource,
188: boolean undo) {
189: dependency.getPredecessor().getSuccessorList().remove(
190: dependency);
191: dependency.getSuccessor().getPredecessorList().remove(
192: dependency);
193: updateSentinels(dependency);
194:
195: if (eventSource != null)
196: dependency.fireDeleteEvent(eventSource);
197:
198: UndoableEditSupport undoableEditSupport = getUndoableEditSupport(dependency);
199: if (undo && undoableEditSupport != null
200: && !(eventSource instanceof UndoableEdit)) {
201: undoableEditSupport.postEdit(new DependencyDeletionEdit(
202: dependency, eventSource));
203: }
204:
205: }
206:
207: public void setFields(Dependency dependency, long lag, int type,
208: Object eventSource) throws InvalidAssociationException {
209:
210: // if (eventSource != null)
211: // dependency.getDocument().getObjectEventManager().fireUpdateEvent(eventSource,dependency);
212: long oldLag = dependency.getLag();
213: int oldType = dependency.getDependencyType();
214: dependency.setLag(lag);
215: dependency.setDependencyType(type);
216:
217: UndoableEditSupport undoableEditSupport = getUndoableEditSupport(dependency);
218: if (undoableEditSupport != null
219: && !(eventSource instanceof UndoableEdit)) {
220: undoableEditSupport.postEdit(new DependencySetFieldsEdit(
221: dependency, oldLag, oldType, eventSource));
222: }
223:
224: }
225:
226: public void update(Dependency dependency, Object eventSource) {
227: if (eventSource != null)
228: dependency.fireUpdateEvent(eventSource);
229:
230: }
231:
232: // update the starting and ending sentinels of the project - the sentinels keep track of which
233: // tasks have no preds or no successors
234: public void updateSentinels(Dependency dependency) {
235: Task predecessor = (Task) dependency.getPredecessor();
236: Task successor = (Task) dependency.getSuccessor();
237: predecessor.updateEndSentinel();
238: successor.updateStartSentinel();
239: }
240:
241: /**
242: * Connect tasks sequentially.
243: * Circularities will be tested, and an exception thrown if any circularity would occur
244: *
245: * @param tasks
246: * @param eventSource
247: * @throws InvalidAssociationException
248: */
249: public void connect(List tasks, Object eventSource,
250: Predicate canBeSuccessorCondition)
251: throws InvalidAssociationException {
252: ArrayList newDependencies = new ArrayList();
253: // try making new dependencies between all items earlier to all items later, thereby checking all possible circularities
254: HasDependencies pred;
255: HasDependencies succ;
256: Object temp;
257: for (int i = 0; i < tasks.size() - 1; i++) {
258: temp = tasks.get(i);
259: if (!(temp instanceof HasDependencies))
260: continue;
261: pred = (HasDependencies) temp;
262: for (int j = i + 1; j < tasks.size(); j++) {
263: temp = tasks.get(j);
264: if (!(temp instanceof HasDependencies))
265: continue;
266: succ = (HasDependencies) temp;
267: if (canBeSuccessorCondition != null
268: && !canBeSuccessorCondition.evaluate(succ)) // allow exclusion of certain nodes that we don't want to be successors
269: continue;
270: if (succ.getPredecessorList().findLeft(pred) != null) // if dependency already exists, skip it
271: continue;
272: Dependency test = Dependency.getInstance(pred, succ,
273: DependencyType.FS, 0); // make a new one
274: test.testValid(false); // test for circularity, throws if bad
275: if (j == i + 1) // only add sequential ones
276: newDependencies.add(test);
277: }
278: }
279: Iterator d = newDependencies.iterator();
280: while (d.hasNext()) {
281: connect((Dependency) d.next(), eventSource);
282: }
283:
284: }
285:
286: /**
287: * Remove all dependencies between all tasks in an array
288: * @param tasks
289: * @param eventSource
290: */
291: public void removeAnyDependencies(List tasks, Object eventSource) {
292: HasDependencies pred;
293: HasDependencies succ;
294: Object temp;
295: for (int i = 0; i < tasks.size() - 1; i++) {
296: temp = tasks.get(i);
297: if (!(temp instanceof HasDependencies))
298: continue;
299: pred = (HasDependencies) temp;
300: for (int j = i + 1; j < tasks.size(); j++) {
301: temp = tasks.get(j);
302: if (!(temp instanceof HasDependencies))
303: continue;
304: succ = (HasDependencies) temp;
305: removeAnyDependencies(pred, succ, eventSource);
306: }
307: }
308: }
309:
310: public void removeAnyDependencies(HasDependencies first,
311: HasDependencies second, Object eventSource) {
312: Dependency dependency;
313: if (first == null || second == null)
314: return;
315: if ((dependency = (Dependency) first.getPredecessorList()
316: .findLeft(second)) != null)
317: remove(dependency, eventSource, true);
318: if ((dependency = (Dependency) second.getPredecessorList()
319: .findLeft(first)) != null)
320: remove(dependency, eventSource, true);
321: if ((dependency = (Dependency) first.getSuccessorList()
322: .findRight(second)) != null)
323: remove(dependency, eventSource, true);
324: if ((dependency = (Dependency) second.getSuccessorList()
325: .findRight(first)) != null)
326: remove(dependency, eventSource, true);
327: }
328:
329: public void remove(Collection dependencyList, Object eventSource) {
330: Dependency dependency;
331: Iterator i = dependencyList.iterator();
332: while (i.hasNext()) {
333: dependency = (Dependency) i.next();
334: remove(dependency, eventSource, true);
335: }
336: }
337:
338: //fix
339: public void remove(Collection dependencyList, Collection toRemove) {
340: Iterator i = dependencyList.iterator();
341: while (i.hasNext())
342: toRemove.add(i.next());
343: }
344:
345: //undo
346: public UndoableEditSupport getUndoableEditSupport(
347: Dependency dependency) {
348: if (dependency.getPredecessor() == null)
349: return null;
350: else {
351: DataFactoryUndoController c = ((Task) dependency
352: .getPredecessor()).getProject().getUndoController();
353: if (c == null)
354: return null;
355: return c.getEditSupport();
356: }
357: }
358:
359: public static String getCircularCrossProjectLinkMessage(
360: Object predecessor, Object successor) {
361: return MessageFormat
362: .format(
363: Messages
364: .getString("Message.crossProjectCircularDependency.mf"),
365: new Object[] { predecessor, successor });
366: }
367:
368: /**
369: * Warn that a cross project link is disabled. This is invoked later to give time for the gantt to redraw first
370: * @param predecessor
371: * @param successor
372: */
373: public static void warnCircularCrossProjectLinkMessage(
374: final Object predecessor, final Object successor) {
375: if (Alert.allowPopups()) {
376: SwingUtilities.invokeLater(new Runnable() {
377: public void run() {
378: Alert.warn(getCircularCrossProjectLinkMessage(
379: predecessor, successor));
380: }
381: });
382: }
383: }
384: }
|