001: package com.technoetic.xplanner.forms;
002:
003: import java.io.Serializable;
004: import java.text.ParseException;
005: import java.util.ArrayList;
006: import java.util.Calendar;
007: import java.util.Date;
008: import java.util.HashSet;
009: import java.util.Iterator;
010: import javax.servlet.http.HttpServletRequest;
011:
012: import org.apache.commons.lang.StringUtils;
013: import org.apache.struts.action.ActionErrors;
014: import org.apache.struts.action.ActionMapping;
015:
016: import com.technoetic.xplanner.format.DateTimeFormat;
017: import com.technoetic.xplanner.util.Interval;
018: import com.technoetic.xplanner.util.RequestUtils;
019:
020: public class TimeEditorForm extends AbstractEditorForm {
021: public static final int MAX_DESCRIPTION_LENGTH = 500;
022: public static final int HOURS = 60 * 60 * 1000;
023: public static final int HOUR_IN_MS = HOURS;
024: public static final String WIZARD_MODE_ATTR = "wizard_mode";
025:
026: private ArrayList ids = new ArrayList();
027: private ArrayList deletes = new ArrayList();
028: private ArrayList startTimes = new ArrayList();
029: private ArrayList endTimes = new ArrayList();
030: private ArrayList durations = new ArrayList();
031: private ArrayList people1 = new ArrayList();
032: private ArrayList people2 = new ArrayList();
033: private ArrayList reportDates = new ArrayList();
034: private ArrayList descriptions = new ArrayList();
035:
036: private HashSet previousMementos = new HashSet();
037: private String remainingHours;
038:
039: private int rowcount;
040: public static final String UNPARSABLE_TIME_ERROR_KEY = "edittime.error.unparsable_time";
041: public static final String UNPARSABLE_NUMBER_ERROR_KEY = "edittime.error.unparsable_number";
042: public static final String MISSING_TIME_ERROR_KEY = "edittime.error.missing_time";
043: public static final String MISSING_PERSON_ERROR_KEY = "edittime.error.missing_person";
044: public static final String SAME_PEOPLE_ERROR_KEY = "edittime.error.same_people";
045: public static final String NEGATIVE_INTERVAL_ERROR_KEY = "edittime.error.negative_interval";
046: public static final String OVERLAPPING_INTERVAL_ERROR_KEY = "edittime.error.overlapping_interval";
047: public static final String BOTH_INTERVAL_AND_DURATION_ERROR_KEY = "edittime.error.both_interval_and_duration";
048: public static final String MISSING_REPORT_DATE_ERROR_KEY = "edittime.error.missing_report_date";
049: public static final String LONG_DESCRIPTION_ERROR_KEY = "edittime.error.long_description";
050:
051: public ActionErrors validate(ActionMapping mapping,
052: HttpServletRequest request) {
053: initConverters(request);
054:
055: ActionErrors errors = new ActionErrors();
056: previousMementos.clear();
057:
058: for (int i = 0; i < rowcount; i++) {
059: errors.add(valideRow(i, request));
060: }
061: return errors;
062: }
063:
064: private ActionErrors valideRow(int row, HttpServletRequest request) {
065: ActionErrors errors = new ActionErrors();
066: if (row == rowcount - 1 && isEmpty(row))
067: return errors;
068:
069: int id = 0;
070: if (isPresent(getEntryId(row))) {
071: id = Integer.parseInt(getEntryId(row));
072: }
073:
074: if (getDeleted(row) != null && getDeleted(row).equals("true")) {
075: return errors;
076: }
077:
078: String startTimeString = getStartTime(row);
079: String endTimeString = getEndTime(row);
080: Date startTime = convertToDateTime(startTimeString,
081: UNPARSABLE_TIME_ERROR_KEY, errors);
082: Date endTime = convertToDateTime(endTimeString,
083: UNPARSABLE_TIME_ERROR_KEY, errors);
084: Date reportDate = convertToDate(getReportDate(row),
085: UNPARSABLE_TIME_ERROR_KEY, errors);
086:
087: int person1Id = 0;
088: if (isPresent(getPerson1Id(row))) {
089: person1Id = Integer.parseInt(getPerson1Id(row));
090: }
091: int person2Id = 0;
092: if (isPresent(getPerson2Id(row))) {
093: person2Id = Integer.parseInt(getPerson2Id(row));
094: }
095:
096: double duration = 0;
097: if (isPresent(getDuration(row))) {
098: try {
099: duration = decimalConverter.parse(getDuration(row));
100: } catch (ParseException ex) {
101: error(errors, UNPARSABLE_NUMBER_ERROR_KEY);
102: }
103: }
104:
105: // Validation #1
106: // - Start and end must be present in all except the last row
107: if (row < getRowcount() - 1
108: && (startTime == null || endTime == null)
109: && duration == 0) {
110: error(errors, MISSING_TIME_ERROR_KEY);
111: }
112:
113: // Validation #2
114: // - At least one person must be present in all except the last row
115: // unless the last row has no time entry
116: if ((id > 0 || startTime != null || endTime != null || duration > 0)
117: && person1Id == 0 && person2Id == 0) {
118: error(errors, MISSING_PERSON_ERROR_KEY);
119: } else
120: // Validation #5
121: // - Different people
122: if (startTime != null && person1Id == person2Id) {
123: error(errors, SAME_PEOPLE_ERROR_KEY);
124: }
125:
126: // Validation #3
127: // - End time must be greater than start time
128: if (startTime != null && endTime != null
129: && endTime.getTime() <= startTime.getTime()) {
130: error(errors, NEGATIVE_INTERVAL_ERROR_KEY);
131: } else
132: // Validation #4
133: // - no overlapping intervals
134: if (isOverlapping(startTime, endTime, duration, person1Id,
135: person2Id)) {
136: error(errors, OVERLAPPING_INTERVAL_ERROR_KEY);
137: }
138:
139: // Validation #6
140: // - End time and Duration
141: if (startTime == null && endTime != null && duration != 0.0) {
142: error(errors, MISSING_TIME_ERROR_KEY);
143: }
144:
145: // Validation #7
146: // - Start time and Duration -- calculate end time
147: if (startTime != null && endTime != null && duration != 0.0
148: && row == rowcount - 1) {
149: // This recovers automatically, no error message
150: error(errors, BOTH_INTERVAL_AND_DURATION_ERROR_KEY);
151: }
152:
153: // Validation #8
154: // - Start time and Duration -- calculate end time
155: if (startTime != null && endTime == null && duration != 0.0) {
156: // This recovers automatically, no error message
157: Calendar calendar = Calendar.getInstance();
158: calendar.setTime(startTime);
159: calendar
160: .add(Calendar.MILLISECOND, (int) (duration * HOURS));
161: setEndTime(row, DateTimeFormat.format(request, calendar
162: .getTime()));
163: }
164:
165: // Validation #9
166: // - Report Date must be present
167: if (reportDate == null) {
168: error(errors, MISSING_REPORT_DATE_ERROR_KEY);
169: }
170:
171: if (isPresent(getDescription(row))) {
172: if (getDescription(row).length() > MAX_DESCRIPTION_LENGTH) {
173: error(errors, LONG_DESCRIPTION_ERROR_KEY);
174: }
175: }
176: return errors;
177: }
178:
179: public void reset(ActionMapping mapping, HttpServletRequest request) {
180: if (!RequestUtils.isAttributeTrue(request, WIZARD_MODE_ATTR)) {
181: super .reset(mapping, request);
182: ids.clear();
183: deletes.clear();
184: startTimes.clear();
185: endTimes.clear();
186: people1.clear();
187: people2.clear();
188: durations.clear();
189: reportDates.clear();
190: descriptions.clear();
191: rowcount = 0;
192: }
193: }
194:
195: public void setEntryId(int index, String id) {
196: ensureSize(ids, index + 1);
197: ids.set(index, id);
198: }
199:
200: public String getEntryId(int index) {
201: ensureSize(ids, index + 1);
202: return (String) ids.get(index);
203: }
204:
205: public void setDeleted(int index, String flag) {
206: ensureSize(deletes, index + 1);
207: deletes.set(index, flag);
208: }
209:
210: public String getDeleted(int index) {
211: ensureSize(deletes, index + 1);
212: return (String) deletes.get(index);
213: }
214:
215: public void setStartTime(int index, String date) {
216: ensureSize(startTimes, index + 1);
217: startTimes.set(index, date);
218: }
219:
220: public String getStartTime(int index) {
221: ensureSize(startTimes, index + 1);
222: return (String) startTimes.get(index);
223: }
224:
225: public void setEndTime(int index, String date) {
226: ensureSize(endTimes, index + 1);
227: endTimes.set(index, date);
228: }
229:
230: public String getEndTime(int index) {
231: ensureSize(endTimes, index + 1);
232: return (String) endTimes.get(index);
233: }
234:
235: public void setDuration(int index, String duration) {
236: ensureSize(durations, index + 1);
237: durations.set(index, duration);
238: }
239:
240: public String getDuration(int index) {
241: ensureSize(durations, index + 1);
242: return (String) durations.get(index);
243: }
244:
245: public void setPerson1Id(int index, String id) {
246: ensureSize(people1, index + 1);
247: people1.set(index, id);
248: }
249:
250: public String getPerson1Id(int index) {
251: ensureSize(people1, index + 1);
252: return (String) people1.get(index);
253: }
254:
255: public void setPerson2Id(int index, String id) {
256: ensureSize(people2, index + 1);
257: people2.set(index, id);
258: }
259:
260: public String getPerson2Id(int index) {
261: ensureSize(people2, index + 1);
262: return (String) people2.get(index);
263: }
264:
265: public void setReportDate(int index, String date) {
266: ensureSize(reportDates, index + 1);
267: reportDates.set(index, date);
268: }
269:
270: public String getReportDate(int index) {
271: ensureSize(reportDates, index + 1);
272: return (String) reportDates.get(index);
273: }
274:
275: public void setDescription(int index, String description) {
276: ensureSize(descriptions, index + 1);
277: descriptions.set(index, description);
278: }
279:
280: public String getDescription(int index) {
281: ensureSize(descriptions, index + 1);
282: return (String) descriptions.get(index);
283: }
284:
285: public String getRemainingHours() {
286: return remainingHours;
287: }
288:
289: public void setRemainingHours(String remainingHours) {
290: this .remainingHours = remainingHours;
291: }
292:
293: public void setRowcount(int rowcount) {
294: this .rowcount = rowcount;
295: }
296:
297: public int getRowcount() {
298: return rowcount;
299: }
300:
301: public boolean isIntervalReadOnly(int i) {
302: return !isDurationReadOnly(i) && !isEmpty(i);
303: }
304:
305: public boolean isDurationReadOnly(int i) {
306: return !isEmpty(i)
307: && (StringUtils.isNotEmpty(getStartTime(i)) || StringUtils
308: .isNotEmpty(getEndTime(i)));
309: }
310:
311: public boolean isEmpty(int index) {
312: return StringUtils.isEmpty(getStartTime(index))
313: && StringUtils.isEmpty(getEndTime(index))
314: && StringUtils.isEmpty(getDuration(index));
315: }
316:
317: private class TimeEntryMemento implements Serializable {
318: private int personId1;
319: private int personId2;
320: private Interval interval;
321:
322: public TimeEntryMemento(int personId1, int personId2,
323: Date startTime, Date endTime) {
324: this .personId1 = personId1;
325: this .personId2 = personId2;
326: if (startTime != null && endTime != null) {
327: interval = new Interval(startTime.getTime(), endTime
328: .getTime());
329: } else if (startTime != null) {
330: interval = new Interval(startTime.getTime());
331: } else if (endTime != null) {
332: interval = new Interval(endTime.getTime());
333: }
334: }
335:
336: public int getPersonId1() {
337: return personId1;
338: }
339:
340: public int getPersonId2() {
341: return personId2;
342: }
343:
344: public Interval getInterval() {
345: return interval;
346: }
347:
348: public boolean overlaps(TimeEntryMemento previousMemento) {
349: return interval.overlaps(previousMemento.getInterval())
350: && (personId1 != 0
351: && (personId1 == previousMemento
352: .getPersonId1() || personId1 == previousMemento
353: .getPersonId2()) || personId2 != 0
354: && (personId2 == previousMemento
355: .getPersonId1() || personId2 == previousMemento
356: .getPersonId2()));
357: }
358: }
359:
360: private boolean isOverlapping(Date startTime, Date endTime,
361: double duration, int personId1, int personId2) {
362: if (startTime != null && (endTime != null || duration > 0)) {
363: if (endTime == null) {
364: endTime = new Date(
365: (long) (startTime.getTime() + duration
366: * HOUR_IN_MS));
367: }
368: TimeEntryMemento memento = new TimeEntryMemento(personId1,
369: personId2, startTime, endTime);
370: if (memento.getInterval() != null) {
371: Iterator mementoItr = previousMementos.iterator();
372: while (mementoItr.hasNext()) {
373: TimeEntryMemento previousMemento = (TimeEntryMemento) mementoItr
374: .next();
375: if (memento.overlaps(previousMemento)) {
376: return true;
377: }
378: }
379: previousMementos.add(memento);
380: }
381: }
382: return false;
383: }
384: }
|