001: package com.technoetic.xplanner.domain;
002:
003: import java.util.ArrayList;
004: import java.util.Collection;
005: import java.util.Collections;
006: import java.util.Date;
007: import java.util.HashSet;
008: import java.util.Iterator;
009: import java.util.List;
010: import java.util.Map;
011: import java.util.TreeMap;
012:
013: import com.technoetic.xplanner.util.CollectionUtils;
014: import com.technoetic.xplanner.util.CollectionUtils.DoublePropertyFilter;
015:
016: public class Iteration extends DomainObject implements Nameable,
017: NoteAttachable, Describable {
018: private int projectId;
019: private String name;
020: private String description;
021: private IterationStatus status;
022: private Date startDate;
023: private Date endDate;
024: private Collection userStories = new HashSet();
025: private double daysWorked;
026: private double estimatedHours;
027: public static final int STORY_ID_INDEX = 0;
028: public static final int ORDER_NO_INDEX = 1;
029:
030: // --------------------- Interface Describable ---------------------
031:
032: public void setDescription(String description) {
033: this .description = description;
034: }
035:
036: public String getDescription() {
037: return description;
038: }
039:
040: public StoryDisposition determineContinuedStoryDisposition() {
041: StoryDisposition disposition;
042: if (this .isCurrent() && this .isActive()) {
043: disposition = StoryDisposition.ADDED;
044: } else {
045: disposition = StoryDisposition.CARRIED_OVER;
046: }
047: return disposition;
048: }
049:
050: public boolean isCurrent() {
051: return startDate.getTime() <= System.currentTimeMillis()
052: && endDate.getTime() > System.currentTimeMillis();
053: }
054:
055: public int getProjectId() {
056: return projectId;
057: }
058:
059: public void setProjectId(int projectId) {
060: this .projectId = projectId;
061: }
062:
063: // TODO: Once we get a full object model with bi-directional relationship this code must move to the story
064:
065: public StoryDisposition determineNewStoryDisposition() {
066: StoryDisposition disposition;
067: if (this .isActive()) {
068: disposition = StoryDisposition.ADDED;
069: } else {
070: disposition = StoryDisposition.PLANNED;
071: }
072: return disposition;
073: }
074:
075: public boolean isActive() {
076: return IterationStatus.ACTIVE == getStatus();
077: }
078:
079: public double getActualHours() {
080: return getCachedActualHours();
081: }
082:
083: public double getCachedActualHours() {
084: return getSumOfStoryProperty(UserStory.CACHED_TASK_BASED_ACTUAL_HOURS);
085: }
086:
087: public double getTaskActualHours() {
088: return getSumOfStoryProperty(UserStory.TASK_BASED_ACTUAL_HOURS);
089: }
090:
091: public double getEstimatedHoursOfAddedTasks() {
092: return getSumOfStoryProperty(UserStory.TASK_BASED_ADDED_HOURS);
093: }
094:
095: public double getTaskAddedHours() {
096: return getSumOfStoryProperty(UserStory.TASK_ESTIMATED_HOURS_IF_STORY_ADDED);
097: }
098:
099: public double getAdjustedEstimatedHours() {
100: return getSumOfStoryProperty(UserStory.TASK_BASED_ADJUSTED_ESTIMATED_HOURS);
101: }
102:
103: public double getTaskActualCompletedHours() {
104: return getSumOfStoryProperty(UserStory.TASK_BASED_COMPLETED_HOURS);
105: }
106:
107: public double getStoryCompletedHours() {
108: return getSumOfStoryProperty(UserStory.COMPLETED_HOURS);
109: }
110:
111: public double getTaskCompletedHours() {
112: return getSumOfStoryProperty(UserStory.TASK_BASED_COMPLETED_ORIGINAL_HOURS);
113: }
114:
115: public double getDaysWorked() {
116: return daysWorked;
117: }
118:
119: public void setDaysWorked(double daysWorked) {
120: this .daysWorked = daysWorked;
121: }
122:
123: public Date getEndDate() {
124: return endDate;
125: }
126:
127: public void setEndDate(Date endDate) {
128: this .endDate = endDate;
129: }
130:
131: public double getEstimatedHours() {
132: if (estimatedHours != 0) {
133: return estimatedHours;
134: }
135:
136: estimatedHours = getTaskCurrentEstimatedHours();
137: return estimatedHours;
138: }
139:
140: public double getTaskCurrentEstimatedHours() {
141: return getSumOfStoryProperty(UserStory.ESTIMATED_HOURS);
142: }
143:
144: public String getName() {
145: return name;
146: }
147:
148: public void setName(String name) {
149: this .name = name;
150: }
151:
152: public double getAddedOriginalHours() {
153: return getSumOfStoryProperty(UserStory.TASK_BASED_ADDED_ORIGINAL_HOURS);
154: }
155:
156: /**
157: * @return sum(task.originalEstimate) where task.isComplete=true
158: */
159: public double getCompletedOriginalHours() {
160: return getSumOfStoryProperty(UserStory.TASK_BASED_COMPLETED_ORIGINAL_HOURS);
161: }
162:
163: public double getTaskEstimatedOriginalHours() {
164: return getSumOfStoryProperty(UserStory.TASK_BASED_ESTIMATED_ORIGINAL_HOURS);
165: }
166:
167: public double getOverestimatedOriginalHours() {
168: return getSumOfStoryProperty(UserStory.TASK_BASED_OVERESTIMATED_ORIGINAL_HOURS);
169: }
170:
171: public double getUnderestimatedOriginalHours() {
172: return getSumOfStoryProperty(UserStory.TASK_BASED_UNDERESTIMATED_ORIGINAL_HOURS);
173: }
174:
175: public double getTaskOverestimatedHours() {
176: return getSumOfStoryProperty(UserStory.TASK_BASED_OVERESTIMATED_HOURS);
177: }
178:
179: public double getPostponedHours() {
180: return getSumOfStoryProperty(UserStory.TASK_BASED_POSTPONED_HOURS);
181: }
182:
183: public double getStoryRemainingHours() {
184: return getSumOfStoryProperty(UserStory.REMAINING_HOURS)
185: - getStoryPostponedHours();
186: }
187:
188: public double getRemainingHours() {
189: return getTaskRemainingHours();
190: }
191:
192: public double getTaskRemainingHours() {
193: return getSumOfStoryProperty(UserStory.TASK_BASED_REMAINING_HOURS);
194: }
195:
196: public double getTaskCompletedRemainingHours() {
197: return getSumOfStoryProperty(UserStory.TASK_BASED_COMPLETED_REMAINING_HOURS)
198: - getTaskPostponedHours();
199: }
200:
201: public Date getStartDate() {
202: return startDate;
203: }
204:
205: public void setStartDate(Date startDate) {
206: this .startDate = startDate;
207: }
208:
209: public IterationStatus getStatus() {
210: return status;
211: }
212:
213: public void setStatus(IterationStatus status) {
214: this .status = status;
215: }
216:
217: public String getStatusKey() {
218: return status != null ? status.getKey() : null;
219: }
220:
221: public double getTaskUnderestimatedOriginalHours() {
222: return getSumOfStoryProperty(UserStory.TASK_BASED_UNDERESTIMATED_ORIGINAL_HOURS);
223: }
224:
225: public double getTaskUnderestimatedHours() {
226: return getSumOfStoryProperty(UserStory.TASK_BASED_UNDERESTIMATED_HOURS);
227: }
228:
229: public Collection getUserStories() {
230: return userStories;
231: }
232:
233: public void setUserStories(Collection userStories) {
234: this .userStories = userStories;
235: }
236:
237: public boolean isFuture() {
238: return startDate.getTime() > System.currentTimeMillis();
239: }
240:
241: public void setStatusKey(String status) {
242: this .status = IterationStatus.fromKey(status);
243: }
244:
245: public void start() {
246: setStatus(IterationStatus.ACTIVE);
247: startStories();
248: }
249:
250: private void startStories() {
251: for (Iterator it = getUserStories().iterator(); it.hasNext();) {
252: UserStory story = (UserStory) it.next();
253: story.start();
254: }
255: }
256:
257: public void close() {
258: setStatus(IterationStatus.INACTIVE);
259: }
260:
261: private double getSumOfStoryProperty(String name) {
262: return CollectionUtils.sum(getUserStories(),
263: new DoublePropertyFilter(name));
264: }
265:
266: public String toString() {
267: return "Iteration{" + "id='" + getId() + ", " + "projectId='"
268: + projectId + ", " + "name='" + name + "'" + "}";
269: }
270:
271: public double getTaskPostponedHours() {
272: return getSumOfStoryProperty(UserStory.TASK_BASED_POSTPONED_HOURS);
273: }
274:
275: public double getStoryPostponedHours() {
276: return getSumOfStoryProperty(UserStory.POSTPONED_STORY_HOURS);
277: }
278:
279: public double getTaskTotalHours() {
280: return getTaskOriginalHours() + getTaskAddedHours()
281: - getTaskPostponedHours();
282: }
283:
284: public double getStoryTotalHours() {
285: return getStoryOriginalHours() + getStoryAddedHours()
286: - getStoryPostponedHours();
287: }
288:
289: public double getStoryAddedHours() {
290: return getSumOfStoryProperty(UserStory.STORY_ESTIMATED_HOURS_IF_STORY_ADDED);
291: }
292:
293: public double getStoryEstimatedHours() {
294: return getSumOfStoryProperty(UserStory.STORY_ESTIMATED_HOURS);
295: }
296:
297: public double getStoryOriginalHours() {
298: return getSumOfStoryProperty(UserStory.STORY_ESTIMATED_ORIGINAL_HOURS);
299: }
300:
301: public double getTaskOriginalHours() {
302: return getSumOfStoryProperty(UserStory.TASK_ESTIMATED_HOURS_IF_ORIGINAL_STORY);
303: }
304:
305: public String getNewTaskDispositionName(UserStory story) {
306: TaskDisposition disposition = getNewTaskDisposition(story);
307: return disposition.getName();
308: }
309:
310: public TaskDisposition getNewTaskDisposition(UserStory story) {
311: TaskDisposition disposition;
312: // if(story.getDisposition().equals(StoryDisposition.ADDED){
313: // disposition = TaskDisposition.DISCOVERED;
314: // }
315: if (story.isStarted()) {
316: disposition = TaskDisposition.DISCOVERED;
317: } else {
318: if (isActive()) {
319: disposition = TaskDisposition.DISCOVERED;
320: } else {
321: disposition = TaskDisposition.PLANNED;
322: }
323: }
324: return disposition;
325: }
326:
327: public void modifyStoryOrder(int[][] storyIdAndNewOrder) {
328: Map storiesById = mapStoriesById(getUserStories());
329: List orderChanges = getOrderChanges(storyIdAndNewOrder,
330: storiesById);
331: reorderStories(orderChanges);
332: }
333:
334: private Map mapStoriesById(Collection stories) {
335: Map storiesById = new TreeMap();
336: for (Iterator iterator = stories.iterator(); iterator.hasNext();) {
337: UserStory userStory = (UserStory) iterator.next();
338: storiesById.put(new Integer(userStory.getId()), userStory);
339: }
340: return storiesById;
341: }
342:
343: private void reorderStories(List storiesByOrder) {
344: for (int index = 0, newOrderNo = 1; index < storiesByOrder
345: .size(); index++, newOrderNo++) {
346: UserStory orderNoChange = (UserStory) storiesByOrder
347: .get(index);
348: orderNoChange.setOrderNo(newOrderNo);
349: }
350: }
351:
352: private List getOrderChanges(int[][] storyIdAndNewOrder,
353: Map storiesById) {
354: List storiesByOrder = new ArrayList();
355: for (int index = 0; index < storyIdAndNewOrder.length; index++) {
356: Integer storyId = new Integer(
357: storyIdAndNewOrder[index][STORY_ID_INDEX]);
358: int newOrderNo = storyIdAndNewOrder[index][ORDER_NO_INDEX];
359: UserStory userStory = (UserStory) storiesById.get(storyId);
360: userStory.setOrderNo(newOrderNo);
361: storiesByOrder.add(userStory);
362: }
363: Collections.sort(storiesByOrder,
364: new StoryOrderNoChangeComparator());
365: return storiesByOrder;
366: }
367: }
|