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 com.projity.association.Association;
053: import com.projity.association.InvalidAssociationException;
054: import com.projity.datatype.Duration;
055: import com.projity.datatype.DurationFormat;
056: import com.projity.document.Document;
057: import com.projity.pm.calendar.WorkCalendar;
058: import com.projity.pm.criticalpath.ScheduleWindow;
059: import com.projity.pm.task.BelongsToDocument;
060: import com.projity.pm.task.Task;
061: import com.projity.server.data.DataObject;
062: import com.projity.strings.Messages;
063:
064: public class Dependency implements Association, BelongsToDocument,
065: DataObject {
066: static final long serialVersionUID = 283794049292031L;
067: private int dependencyType;
068: private transient HasDependencies predecessor;
069: private transient HasDependencies successor;
070: private transient boolean disabled = false;
071: private long lag;
072:
073: private transient long earlyDate;
074: private transient long lateDate;
075: public static final long NEEDS_CALCULATION = -1;
076:
077: public static Dependency getInstance(HasDependencies predecessor,
078: HasDependencies successor) {
079: return getInstance(predecessor, successor, DependencyType.FS, 0);
080: }
081:
082: public static Dependency getInstance(HasDependencies predecessor,
083: HasDependencies successor, int dependencyType, long lead) {
084: return new Dependency(predecessor, successor, dependencyType,
085: lead);
086: }
087:
088: private Dependency(HasDependencies predecessor,
089: HasDependencies successor, int dependencyType, long lead) {
090: this .predecessor = predecessor;
091: this .successor = successor;
092: this .dependencyType = dependencyType;
093: this .lag = lead;
094: }
095:
096: public void updateDependencyLists() {
097: predecessor.getSuccessorList().add(this );
098: successor.getPredecessorList().add(this );
099: }
100:
101: public HasDependencies getPredecessor() {
102: return predecessor;
103: }
104:
105: public HasDependencies getSuccessor() {
106: return successor;
107: }
108:
109: public long getLag() {
110: return lag;
111: }
112:
113: public void setLag(long lead) {
114: this .lag = lead;
115: }
116:
117: /**
118: * Copy the fields lag and type
119: *
120: */
121: public void copyPrincipalFieldsFrom(Association from) {
122: this .lag = ((Dependency) from).lag;
123: this .dependencyType = ((Dependency) from).dependencyType;
124:
125: }
126:
127: /**
128: * @return Returns the calendar.
129: */
130: public final WorkCalendar getEffectiveWorkCalendar() {
131: return predecessor.getHasCalendar().getEffectiveWorkCalendar(); // use
132: // the
133: // predecessor's
134: // calendar
135: }
136:
137: /**
138: * @param predecessor
139: * The predecessor to set.
140: */
141: public void setPredecessor(HasDependencies predecessor) {
142: this .predecessor = predecessor;
143: }
144:
145: public void setSuccessor(HasDependencies successor) {
146: this .successor = successor;
147: }
148:
149: /**
150: * @return Returns the dependencyType.
151: */
152: public int getDependencyType() {
153: return dependencyType;
154: }
155:
156: /**
157: * @param dependencyType
158: * The dependencyType to set.
159: * @throws InvalidAssociationException
160: */
161: public void setDependencyType(int dependencyType)
162: throws InvalidAssociationException {
163: if (((Task) getSuccessor()).isWbsParent()) {
164: if (dependencyType == DependencyType.FF
165: || dependencyType == DependencyType.SF) {
166: throw new InvalidAssociationException(
167: Messages
168: .getString("Message.parentSuccessorCannotHaveFinishLink"));
169: }
170: }
171:
172: this .dependencyType = dependencyType;
173: }
174:
175: boolean isCircular() {
176: return predecessor.dependsOn(successor);
177: }
178:
179: boolean isLinkToParent() {
180: return ((Task) successor).wbsDescendentOf((Task) predecessor);
181: }
182:
183: boolean isLinkToChild() {
184: return ((Task) predecessor).wbsDescendentOf((Task) successor);
185: }
186:
187: public void testValid(boolean allowDuplicate)
188: throws InvalidAssociationException {
189: if (isCircular())
190: throw new InvalidAssociationException(Messages
191: .getString("Message.circularDependency"));
192:
193: if (isLinkToParent() || isLinkToChild())
194: throw new InvalidAssociationException(Messages
195: .getString("Message.cannotLinkToSummary"));
196:
197: if (!allowDuplicate
198: && predecessor.getSuccessorList().findRight(successor) != null)
199: throw new InvalidAssociationException(Messages
200: .getString("Message.cannotLinkTwice"));
201: }
202:
203: public Object getLeft() {
204: return predecessor;
205: }
206:
207: /* (non-Javadoc)
208: * @see com.projity.util.Association#getRight()
209: */
210: public Object getRight() {
211: return successor;
212: }
213:
214: public void doAddService(Object eventSource) {
215: DependencyService.getInstance().connect((Dependency) this ,
216: eventSource);
217: }
218:
219: public void doRemoveService(Object eventSource) {
220: DependencyService.getInstance().remove((Dependency) this ,
221: eventSource, true);
222: }
223:
224: public void doUpdateService(Object eventSource) {
225: DependencyService.getInstance().update((Dependency) this ,
226: eventSource);
227: }
228:
229: public boolean isDefault() {
230: return false;
231: }
232:
233: public Document getDocument() {
234: return ((BelongsToDocument) getSuccessor()).getDocument();
235: }
236:
237: public boolean refersToDocument(Document document) {
238: return ((Task) getSuccessor()).getMasterDocument() == document
239: || ((Task) getPredecessor()).getMasterDocument() == document;
240:
241: }
242:
243: public void fireCreateEvent(Object eventSource) {
244: ((Task) getSuccessor()).getMasterDocument()
245: .getObjectEventManager().fireCreateEvent(eventSource,
246: this );
247: // if (isExternal())
248: // ((Task)getPredecessor()).getMasterDocument().getObjectEventManager().fireCreateEvent(eventSource,this);
249: }
250:
251: public void fireUpdateEvent(Object eventSource) {
252: ((Task) getSuccessor()).getMasterDocument()
253: .getObjectEventManager().fireUpdateEvent(eventSource,
254: this );
255: // if (isExternal())
256: // ((Task)getPredecessor()).getMasterDocument().getObjectEventManager().fireUpdateEvent(eventSource,this);
257: }
258:
259: public void fireDeleteEvent(Object eventSource) {
260: ((Task) getSuccessor()).getMasterDocument()
261: .getObjectEventManager().fireDeleteEvent(eventSource,
262: this );
263: // if (isExternal())
264: // ((Task)getPredecessor()).getMasterDocument().getObjectEventManager().fireDeleteEvent(eventSource,this);
265: }
266:
267: public boolean isExternal() {
268: return ((Task) getSuccessor()).getProjectId() != ((Task) getPredecessor())
269: .getProjectId();
270: }
271:
272: public boolean isCrossProject() {
273: return isExternal(); // || ((Task)predecessor).isExternal() || ((Task)successor).isExternal();
274: }
275:
276: public String toString() {
277: return "[predecessor]" + predecessor + " [successor]"
278: + successor;
279: }
280:
281: public String getPredecessorName() {
282: return ((Task) predecessor).getName();
283: }
284:
285: public String getSuccessorName() {
286: return ((Task) successor).getName();
287: }
288:
289: public String getQualifiedPredecessorName() {
290: if (isExternal())
291: return ((Task) predecessor).getTaskAndProjectName();
292: else
293: return predecessor.toString();
294: }
295:
296: public String getQualifiedSuccessorName() {
297: if (isExternal())
298: return ((Task) successor).getTaskAndProjectName();
299: else
300: return successor.toString();
301: }
302:
303: public long getPredecessorId() {
304: return ((Task) predecessor).getUniqueId();
305: }
306:
307: public long getSuccessorId() {
308: return ((Task) successor).getUniqueId();
309: }
310:
311: //DataObject
312: public String getName() {
313: // TODO Auto-generated method stub
314: return null;
315: }
316:
317: public void setName(String name) {
318: // TODO Auto-generated method stub
319:
320: }
321:
322: public long getUniqueId() {
323: // TODO Auto-generated method stub
324: return 0;
325: }
326:
327: public void setUniqueId(long id) {
328: // TODO Auto-generated method stub
329:
330: }
331:
332: public String getUniqueIdString() {
333: return getPredecessorId() + "." + getSuccessorId();
334: }
335:
336: transient boolean newId = true;
337:
338: public boolean isNew() {
339: return newId;
340: }
341:
342: public void setNew(boolean newId) {
343: this .newId = newId;
344: }
345:
346: /**
347: * Method to calculate the lead in millis from the lead value stored in the
348: * dependency. In normal cases, it just extracts the milliseconds, but if
349: * the lead is a percentage, then it is calculated based on predecessor
350: * duration. If the value is expressed as elapsed %, then the flag elapsed
351: * is applied to the resulting value. In this case, the rule is that the
352: * lead itself has an elapsed duration which is calculated based on the
353: * tasks (non-elapsed) duration.
354: *
355: * @param dependency
356: * @return lead in milliseconds with elapsed flag set if the dependency is
357: * elapsed
358: */
359: public long getLeadValue() {
360: long leadWithUnits = getLag();
361: if (Duration.isPercent(leadWithUnits)) {
362: long lead = Duration.millis(leadWithUnits);
363: float fraction = Duration.getPercentAsDecimal(lead);
364: if (Duration.isElapsed(leadWithUnits)) {
365: leadWithUnits = (long) (((Task) getPredecessor())
366: .getElapsedDuration() * fraction);
367: leadWithUnits = Duration.setAsElapsed(leadWithUnits); // put in elapsed flag
368: } else {
369: leadWithUnits = (long) (((Task) getPredecessor())
370: .getDuration() * fraction);
371: }
372: }
373: return leadWithUnits;
374: }
375:
376: //gets either predecessor or successor
377: public HasDependencies getTask(boolean pred) {
378: return pred ? predecessor : successor;
379: }
380:
381: public long calcDependencyDate(boolean forward, long begin,
382: long end, boolean hasDuration) {
383: return forward ? calcForwardDependencyDate(begin, end,
384: hasDuration) : calcReverseDependencyDate(begin, end,
385: hasDuration);
386: }
387:
388: /**
389: * Calc the date that this dependency will cause its successor task (if forward scheduling) or predecessor (if reverse scheduling)
390: * @param begin
391: * @param end
392: * @param duration
393: * @return
394: */
395: public long calcForwardDependencyDate(long begin, long end,
396: boolean hasDuration) {
397: if (disabled)
398: return earlyDate;
399: long t = 0;
400:
401: boolean canStartAtDayEnd = !hasDuration; // to handle the milestone case
402: switch (dependencyType) {
403: case DependencyType.FS:
404: t = end;
405: break;
406: case DependencyType.SS:
407: t = begin;
408: break;
409: case DependencyType.FF:
410: t = ((ScheduleWindow) successor).calcOffsetFrom(end, end,
411: false, false, canStartAtDayEnd);
412: break;
413: case DependencyType.SF:
414: t = ((ScheduleWindow) successor).calcOffsetFrom(begin,
415: begin, false, false, canStartAtDayEnd);
416: break;
417: }
418: earlyDate = getEffectiveWorkCalendar().add(t, getLeadValue(),
419: canStartAtDayEnd);
420: return earlyDate;
421: }
422:
423: /**
424: * get the latest finish time for the predecessor. The current
425: * ScheduleWindow in the backward pass of cp algo is the predecessor. It is
426: * possible that the milestone handling code needs more work
427: * begin is actually the late finish and end is early finish
428: */
429: public long calcReverseDependencyDate(long begin, long end,
430: boolean hasDuration) {
431: if (disabled)
432: return lateDate;
433: long t = 0;
434: boolean cannotFinishAtDayStart = !hasDuration; // to handle the milestone case
435: switch (getDependencyType()) {
436: case DependencyType.FS:
437: t = end;
438: break;
439: case DependencyType.SS:
440: t = ((ScheduleWindow) getPredecessor()).calcOffsetFrom(end,
441: end, false, false, cannotFinishAtDayStart);
442: break;
443: case DependencyType.FF:
444: t = begin;
445: break;
446: case DependencyType.SF:
447: t = ((ScheduleWindow) getPredecessor()).calcOffsetFrom(
448: begin, begin, false, false, cannotFinishAtDayStart);
449: break;
450: }
451: lateDate = getEffectiveWorkCalendar().add(t, getLeadValue(),
452: cannotFinishAtDayStart);
453: return lateDate;
454: }
455:
456: public long getDate(boolean early) {
457: return early ? earlyDate : lateDate;
458: }
459:
460: public void setDate(boolean early, long date) {
461: System.out.println(this + "setting date to "
462: + new java.util.Date(date));
463: if (early)
464: earlyDate = date;
465: else
466: lateDate = date;
467: }
468:
469: public String htmlString() {
470: StringBuffer s = new StringBuffer();
471: s.append("<html><body>");
472: s.append(Messages.getString("Gantt.tooltip.link")).append(": ");
473: s.append(DependencyType.toLongString(getDependencyType()))
474: .append(" ");
475: s.append(DurationFormat.format(getLag())).append("<br>");
476: s.append(Messages.getString("Gantt.tooltip.from")).append(": ");
477: s.append(getQualifiedPredecessorName()).append("<br>");
478: s.append(Messages.getString("Gantt.tooltip.to")).append(": ");
479: s.append(getQualifiedSuccessorName()).append("<br>");
480: s.append("</body></html>");
481: return s.toString();
482:
483: }
484:
485: //because it implements DataObject, should implement a different interface
486: private transient boolean dirty;
487:
488: public boolean isDirty() {
489: return dirty;
490: }
491:
492: public void setDirty(boolean dirty) {
493: this .dirty = dirty;
494: }
495:
496: public Document getMasterDocument() {
497: return ((Task) getSuccessor()).getMasterDocument();
498: }
499:
500: public void replace(Object newOne, boolean leftObject) {
501: if (leftObject)
502: setPredecessor((HasDependencies) newOne);
503: else
504: setSuccessor((HasDependencies) newOne);
505: }
506:
507: public final boolean isDisabled() {
508: return disabled;
509: }
510:
511: public final void setDisabled(boolean disabled) {
512: this.disabled = disabled;
513: }
514:
515: }
|