001: package com.technoetic.xplanner.domain;
002:
003: import java.util.Collection;
004: import java.util.HashSet;
005: import java.util.Iterator;
006:
007: import com.technoetic.xplanner.util.CollectionUtils;
008: import com.technoetic.xplanner.util.CollectionUtils.DoubleFilter;
009: import com.technoetic.xplanner.util.CollectionUtils.DoublePropertyFilter;
010:
011: public class UserStory extends DomainObject implements Nameable,
012: NoteAttachable, Describable {
013: // ------------------------------ FIELDS ------------------------------
014:
015: private String name;
016: private String description;
017: private int trackerId;
018: private int iterationId;
019: private Collection tasks = new HashSet();
020: private Collection features = new HashSet();
021: private Person customer;
022: private double estimatedHours;
023: private Double estimatedOriginalHours;
024: private double actualHours;
025: private double postponedHours;
026: private double iterationStartEstimatedHours;
027: private int priority;
028: private int orderNo;
029: private int previousOrderNo;
030: private StoryDisposition disposition = StoryDisposition.PLANNED;
031: private StoryStatus status = StoryStatus.DRAFT;
032:
033: public static final String ITERATION_START_ESTIMATED_HOURS = getValidProperty("iterationStartEstimatedHours");
034: public static final String TASK_BASED_ESTIMATED_ORIGINAL_HOURS = getValidProperty("taskBasedEstimatedOriginalHours");
035: public static final String TASK_BASED_COMPLETED_ORIGINAL_HOURS = getValidProperty("taskBasedCompletedOriginalHours");
036: public static final String TASK_BASED_ADDED_ORIGINAL_HOURS = getValidProperty("taskBasedAddedOriginalHours");
037: // public static final String TASK_BASED_REMAINING_ORIGINAL_HOURS = getValidProperty("taskBasedRemainingOriginalHours");
038: // public static final String TASK_BASED_POSTPONED_ORIGINAL_HOURS = getValidProperty("taskBasedPostponedOriginalHours");
039: public static final String TASK_BASED_OVERESTIMATED_ORIGINAL_HOURS = getValidProperty("taskBasedOverestimatedOriginalHours");
040: public static final String TASK_BASED_UNDERESTIMATED_ORIGINAL_HOURS = getValidProperty("taskBasedUnderestimatedOriginalHours");
041: public static final String TASK_BASED_ESTIMATED_HOURS = getValidProperty("estimatedHours");
042: public static final String TASK_BASED_ADJUSTED_ESTIMATED_HOURS = getValidProperty("adjustedEstimatedHours");
043: public static final String TASK_BASED_COMPLETED_HOURS = getValidProperty("completedTaskHours");
044: public static final String CACHED_TASK_BASED_ACTUAL_HOURS = getValidProperty("cachedActualHours");
045: public static final String TASK_BASED_ACTUAL_HOURS = getValidProperty("actualHours");
046: public static final String TASK_BASED_ADDED_HOURS = getValidProperty("estimatedHoursOfAddedTasks");
047: public static final String TASK_BASED_POSTPONED_HOURS = getValidProperty("postponedHours");
048: public static final String TASK_BASED_REMAINING_HOURS = getValidProperty("taskBasedRemainingHours");
049: public static final String TASK_BASED_COMPLETED_REMAINING_HOURS = getValidProperty("taskBasedCompletedRemainingHours");
050: public static final String TASK_BASED_OVERESTIMATED_HOURS = getValidProperty("overestimatedHours");
051: public static final String TASK_BASED_UNDERESTIMATED_HOURS = getValidProperty("underestimatedHours");
052: public static final String ADJUSTED_ESTIMATED_HOURS = getValidProperty("adjustedEstimatedHours");
053: public static final String ESTIMATED_HOURS = getValidProperty("estimatedHours");
054: public static final String STORY_ESTIMATED_HOURS = getValidProperty("totalHours");
055: public static final String REMAINING_HOURS = getValidProperty("remainingHours");
056: public static final String COMPLETED_HOURS = getValidProperty("completedHours");
057: public static final String TOTAL_HOURS = getValidProperty("totalHours");
058: public static final String TASK_BASED_TOTAL_HOURS = getValidProperty("taskBasedTotalHours");
059: public static final String STORY_ESTIMATED_ORIGINAL_HOURS = getValidProperty("nonTaskBasedOriginalHours");
060: public static final String TASK_ESTIMATED_ORIGINAL_HOURS = getValidProperty("taskEstimatedOriginalHours");
061: public static final String TASK_BASED_ORIGINAL_HOURS = getValidProperty("taskBasedOriginalHours");
062: public static final String TASK_ESTIMATED_HOURS_IF_STORY_ADDED = getValidProperty("taskEstimatedHoursIfStoryAdded");
063: public static final String TASK_ESTIMATED_HOURS_IF_ORIGINAL_STORY = getValidProperty("taskEstimatedHoursIfOriginalStory");
064: public static final String STORY_ESTIMATED_HOURS_IF_STORY_ADDED = getValidProperty("storyEstimatedHoursIfStoryAdded");
065: public static final String POSTPONED_STORY_HOURS = getValidProperty("postponedStoryHours");
066:
067: private static String getValidProperty(String property) {
068: return getValidProperty(UserStory.class, property);
069: }
070:
071: // --------------------- GETTER / SETTER METHODS ---------------------
072:
073: public double getCachedActualHours() {
074: if (actualHours == 0) {
075: actualHours = getActualHours();
076: }
077: return actualHours;
078: }
079:
080: public double getActualHours() {
081: return CollectionUtils.sum(getTasks(), new DoubleFilter() {
082: public double filter(Object o) {
083: return ((Task) o).getActualHours();
084: }
085: });
086: }
087:
088: public double getAdjustedEstimatedHours() {
089: double adjustedEstimatedHours = 0;
090: if (!tasks.isEmpty()) {
091: Iterator itr = tasks.iterator();
092: while (itr.hasNext()) {
093: Task task = (Task) itr.next();
094: adjustedEstimatedHours += task
095: .getAdjustedEstimatedHours();
096: }
097: if (adjustedEstimatedHours == 0) {
098: adjustedEstimatedHours = estimatedHours;
099: }
100: } else {
101: adjustedEstimatedHours = getEstimatedHours();
102: }
103: return adjustedEstimatedHours;
104: }
105:
106: public double getEstimatedHours() {
107: if (tasks.size() > 0)
108: return getTaskBasedEstimatedHours();
109: return estimatedHours;
110: }
111:
112: public StoryStatus getStatus() {
113: return status;
114: }
115:
116: public void setStatus(StoryStatus status) {
117: this .status = status;
118: }
119:
120: public Person getCustomer() {
121: return customer;
122: }
123:
124: public void setCustomer(Person customer) {
125: this .customer = customer;
126: }
127:
128: public String getDescription() {
129: return description;
130: }
131:
132: public void setDescription(String description) {
133: this .description = description;
134: }
135:
136: public StoryDisposition getDisposition() {
137: return disposition;
138: }
139:
140: public void setDisposition(StoryDisposition disposition) {
141: this .disposition = disposition;
142: }
143:
144: public Collection getFeatures() {
145: return features;
146: }
147:
148: public void setFeatures(Collection features) {
149: this .features = features;
150: }
151:
152: public int getIterationId() {
153: return iterationId;
154: }
155:
156: public void setIterationId(int iterationId) {
157: this .iterationId = iterationId;
158: }
159:
160: /**
161: * @deprecated Has to be removed after the next release
162: */
163: public double getIterationStartEstimatedHours() {
164: return iterationStartEstimatedHours;
165: }
166:
167: /**
168: * @deprecated Has to be removed after the next release
169: */
170: public void setIterationStartEstimatedHours(
171: double tasksEstimateAtIterationStart) {
172: this .iterationStartEstimatedHours = tasksEstimateAtIterationStart;
173: }
174:
175: public String getName() {
176: return name;
177: }
178:
179: public void setName(String name) {
180: this .name = name;
181: }
182:
183: public void setEstimatedOriginalHours(
184: Double newEstimatedOriginalHours) {
185: this .estimatedOriginalHours = newEstimatedOriginalHours;
186: }
187:
188: public double getEstimatedOriginalHours() {
189: if (isStarted()) {
190: return getEstimatedOriginalHoursField().doubleValue();
191: } else {
192: return getTaskBasedEstimatedOriginalHours();
193: }
194: }
195:
196: public double getTaskBasedEstimatedOriginalHours() {
197: return CollectionUtils.sum(tasks, new DoublePropertyFilter(
198: Task.ITERATION_START_ESTIMATED_HOURS));
199: }
200:
201: //For Hibernate
202: public Double getEstimatedOriginalHoursField() {
203: return estimatedOriginalHours;
204: }
205:
206: // private boolean isAdded() {
207: // return StoryDisposition.ADDED.equals(getType());
208: // }
209:
210: public void setEstimatedOriginalHoursField(
211: Double estimatedOriginalHours) {
212: this .estimatedOriginalHours = estimatedOriginalHours;
213: }
214:
215: public double getPostponedHours() {
216: return postponedHours;
217: }
218:
219: public void setPostponedHours(double postponedHours) {
220: this .postponedHours = postponedHours;
221: }
222:
223: public int getPriority() {
224: return priority;
225: }
226:
227: public void setPriority(int priority) {
228: this .priority = priority;
229: }
230:
231: public double getTaskBasedEstimatedHours() {
232: double taskBasedEstimatedHours = 0;
233: Iterator itr = tasks.iterator();
234: while (itr.hasNext()) {
235: Task task = (Task) itr.next();
236: taskBasedEstimatedHours += task
237: .getEstimatedHoursBasedOnActuals();
238: }
239: return taskBasedEstimatedHours;
240: }
241:
242: public Collection getTasks() {
243: return tasks;
244: }
245:
246: public void setTasks(Collection tasks) {
247: this .tasks = tasks;
248: }
249:
250: public int getTrackerId() {
251: return trackerId;
252: }
253:
254: public void setTrackerId(int trackerId) {
255: this .trackerId = trackerId;
256: }
257:
258: // ------------------------ CANONICAL METHODS ------------------------
259:
260: public String toString() {
261: return "UserStory(id=" + this .getId() + ", iterationId="
262: + iterationId + ", name=" + name + ", trackerId="
263: + this .getTrackerId() + ", name=" + this .getName()
264: + ", title=" + this .getName() + ", lastUpdateTime="
265: + this .getLastUpdateTime() + ")";
266: }
267:
268: // -------------------------- OTHER METHODS --------------------------
269:
270: public double getEstimatedHoursOfAddedTasks() {
271: return CollectionUtils.sum(getTasks(), new DoubleFilter() {
272: public double filter(Object o) {
273: return ((Task) o).getAddedHours();
274: }
275: });
276: }
277:
278: public double getCompletedTaskHours() {
279: return CollectionUtils.sum(getTasks(), new DoubleFilter() {
280: public double filter(Object o) {
281: return ((Task) o).getCompletedHours();
282: }
283: });
284: }
285:
286: public double getCompletedHours() {
287: return isCompleted() ? getEstimatedHoursField() : 0.0;
288: }
289:
290: public int getCustomerId() {
291: if (getCustomer() == null)
292: return 0;
293: return getCustomer().getId();
294: }
295:
296: public String getDispositionName() {
297: return disposition != null ? disposition.getName() : null;
298: }
299:
300: public double getTaskBasedAddedOriginalHours() {
301: return CollectionUtils.sum(getTasks(), new DoubleFilter() {
302: public double filter(Object o) {
303: return ((Task) o).getAddedOriginalHours();
304: }
305: });
306: }
307:
308: public double getTaskBasedTotalHours() {
309: return CollectionUtils.sum(getTasks(), new DoubleFilter() {
310: public double filter(Object o) {
311: return ((Task) o).getEstimatedHours();
312: }
313: });
314: }
315:
316: public double getTaskBasedCompletedOriginalHours() {
317: return CollectionUtils.sum(getTasks(), new DoubleFilter() {
318: public double filter(Object o) {
319: return ((Task) o).getCompletedOriginalHours();
320: }
321: });
322: }
323:
324: public double getTaskBasedOverestimatedOriginalHours() {
325: return CollectionUtils.sum(getTasks(), new DoubleFilter() {
326: public double filter(Object o) {
327: return ((Task) o).getOverestimatedOriginalHours();
328: }
329: });
330: }
331:
332: public double getTaskBasedUnderestimatedOriginalHours() {
333: return CollectionUtils.sum(getTasks(), new DoubleFilter() {
334: public double filter(Object o) {
335: return ((Task) o).getUnderestimatedOriginalHours();
336: }
337: });
338: }
339:
340: public double getOverestimatedHours() {
341: return CollectionUtils.sum(getTasks(), new DoubleFilter() {
342: public double filter(Object o) {
343: return ((Task) o).getOverestimatedHours();
344: }
345: });
346: }
347:
348: public double getTaskBasedCompletedRemainingHours() {
349: if (tasks.size() == 0) {
350: return 0.0;
351: }
352:
353: double remainingHours = 0;
354: Iterator itr = tasks.iterator();
355: while (itr.hasNext()) {
356: Task task = (Task) itr.next();
357: remainingHours += task.getCompletedRemainingHours();
358: }
359: return remainingHours;
360: }
361:
362: public double getTaskBasedRemainingHours() {
363: if (tasks.size() == 0) {
364: return estimatedHours;
365: }
366:
367: double remainingHours = 0;
368: boolean isTaskEstimatePresent = false;
369: Iterator itr = tasks.iterator();
370: while (itr.hasNext()) {
371: Task task = (Task) itr.next();
372: remainingHours += task.getRemainingHours();
373: isTaskEstimatePresent |= (task.getEstimatedHours() > 0);
374: }
375: return isTaskEstimatePresent ? remainingHours : estimatedHours;
376: }
377:
378: public double getRemainingHours() {
379: return isCompleted() ? 0.0 : getEstimatedHoursField();
380: }
381:
382: public double getUnderestimatedHours() {
383: return CollectionUtils.sum(getTasks(), new DoubleFilter() {
384: public double filter(Object o) {
385: return ((Task) o).getUnderestimatedHours();
386: }
387: });
388: }
389:
390: public String getStatusName() {
391: return status != null ? status.getName() : null;
392: }
393:
394: public void setStatusName(String statusName) {
395: this .status = StoryStatus.fromName(statusName);
396: }
397:
398: public boolean isCompleted() {
399: if (tasks.size() > 0) {
400: Iterator itr = tasks.iterator();
401: while (itr.hasNext()) {
402: Task task = (Task) itr.next();
403: if (!task.isCompleted()) {
404: return false;
405: }
406: }
407: return true;
408: } else {
409: return false;
410: }
411: }
412:
413: public void setDispositionName(String dispositionName) {
414: this .disposition = StoryDisposition.valueOf(dispositionName);
415: }
416:
417: public double getTotalHours() {
418: return getEstimatedHoursField();
419: }
420:
421: // For Hibernate
422: protected double getEstimatedHoursField() {
423: return estimatedHours;
424: }
425:
426: public void setEstimatedHours(double estimatedHours) {
427: setEstimatedHoursField(estimatedHours);
428: }
429:
430: // Hibernate
431: protected void setEstimatedHoursField(double estimatedHours) {
432: this .estimatedHours = estimatedHours;
433: }
434:
435: public void start() {
436: if (!isStarted()) {
437: setEstimatedOriginalHours(new Double(
438: getTaskBasedEstimatedOriginalHours()));
439: }
440: for (Iterator iterator = tasks.iterator(); iterator.hasNext();) {
441: Task task = (Task) iterator.next();
442: task.start();
443: }
444: }
445:
446: public double getNonTaskBasedOriginalHours() {
447: return isAdded() ? 0.0 : getEstimatedHoursField();
448: }
449:
450: public double getTaskEstimatedHoursIfStoryAdded() {
451: if (isAdded()) {
452: double result = getTaskBasedEstimatedOriginalHours();
453: result += getSumOfTaskProperty(Task.ADDED_ORIGINAL_HOURS);
454: return result;
455: } else {
456: return 0.0;
457: }
458: }
459:
460: private boolean isAdded() {
461: return StoryDisposition.ADDED.equals(getDisposition());
462: }
463:
464: public double getTaskEstimatedHoursIfOriginalStory() {
465: return isAdded() ? 0.0 : getTaskEstimatedOriginalHours();
466: }
467:
468: public double getTaskBasedOriginalHours() {
469: return isAdded() ? 0.0 : getTaskEstimatedOriginalHours();
470: }
471:
472: public boolean isStarted() {
473: return estimatedOriginalHours != null;
474: }
475:
476: public double getTaskEstimatedOriginalHours() {
477: return getEstimatedOriginalHours();
478: // return getSumOfTaskProperty(Task.ESTIMATED_ORIGINAL_HOURS);
479: }
480:
481: public double getStoryEstimatedHoursIfStoryAdded() {
482: return isAdded() ? getEstimatedHoursField() : 0.0;
483: }
484:
485: private double getSumOfTaskProperty(String name) {
486: return CollectionUtils.sum(getTasks(),
487: new DoublePropertyFilter(name));
488: }
489:
490: public void postponeRemainingHours() {
491: setPostponedHours(getPostponedHours()
492: + getTaskBasedRemainingHours());
493: }
494:
495: public double getPostponedStoryHours() {
496: return getPostponedHours() > 0.0 ? getEstimatedHoursField()
497: : 0.0;
498: }
499:
500: public int getPreviousOrderNo() {
501: return previousOrderNo;
502: }
503:
504: public int getOrderNo() {
505: return orderNo;
506: }
507:
508: public void setOrderNo(int orderNo) {
509: this .previousOrderNo = this .orderNo;
510: this .orderNo = orderNo;
511: }
512:
513: public void moveTo(Iteration targetIteration) {
514: setIterationId(targetIteration.getId());
515: targetIteration.getUserStories().add(this );
516:
517: if (targetIteration.isActive() && !isStarted()) {
518: start();
519: }
520:
521: if (targetIteration.isActive()) {
522: setDisposition(StoryDisposition.ADDED);
523: setTasksDisposition(TaskDisposition.ADDED);
524: } else {
525: setDisposition(StoryDisposition.PLANNED);
526: setTasksDisposition(TaskDisposition.PLANNED);
527: }
528: }
529:
530: private void setTasksDisposition(TaskDisposition disposition) {
531: Iterator iterator = tasks.iterator();
532: while (iterator.hasNext()) {
533: Task task = (Task) iterator.next();
534: task.setDisposition(disposition);
535: }
536: }
537: }
|