View Javadoc

1   package org.kuali.student.enrollment.acal;
2   
3   
4   import java.io.BufferedReader;
5   import java.io.BufferedWriter;
6   import java.io.DataInputStream;
7   import java.io.File;
8   import java.io.FileInputStream;
9   import java.io.FileWriter;
10  import java.io.IOException;
11  import java.io.InputStreamReader;
12  import java.text.SimpleDateFormat;
13  import java.util.ArrayList;
14  import java.util.Arrays;
15  import java.util.Calendar;
16  import java.util.Date;
17  import java.util.GregorianCalendar;
18  import java.util.HashMap;
19  import java.util.HashSet;
20  import java.util.LinkedHashMap;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.Set;
24  
25  public class AcalReferenceDataParser {
26      private List<Map<String, String>> dataSet = new ArrayList<Map<String, String>>();
27      private Map<String, Atp> allAcals = new LinkedHashMap<String, Atp>();
28      private Map<String, Atp> allHolidayCalendars = new LinkedHashMap<String, Atp>();
29  
30      private static final String FALL_ATP_TYPE = "Fall";
31      private static final String WINTER_ATP_TYPE = "Winter";
32      private static final String SPRING_ATP_TYPE = "Spring";
33      private static final String SUMMER_ATP_TYPE = "Summer";
34      private static final String SESSION1_ATP_TYPE = "Session 1";
35      private static final String SESSION2_ATP_TYPE = "Session 2";
36      private static final String FALL_ATP_TYPE_KEY = "kuali.atp.type.Fall";
37      private static final String WINTER_ATP_TYPE_KEY = "kuali.atp.type.Winter";
38      private static final String SPRING_ATP_TYPE_KEY = "kuali.atp.type.Spring";
39      private static final String SUMMER_ATP_TYPE_KEY = "kuali.atp.type.Summer";
40      private static final String SESSION1_ATP_TYPE_KEY = "kuali.atp.type.Session1";
41      private static final String SESSION2_ATP_TYPE_KEY = "kuali.atp.type.Session2";
42  
43      private static final String RECORD_KEY = "table_key";
44  
45      private static final String RECORD_FIRST_DAY = "first_day";
46      private static final String RECORD_CENSUS_DAY = "tenth_day";
47      private static final String RECORD_LAST_DAY_ADD = "last_day_add";
48      private static final String RECORD_LAST_DAY_DROP_NOT = "last_drop_not";
49      private static final String RECORD_DROP_UG = "last_drop_ug";
50      private static final String RECORD_DROP_GRAD = "last_drop_grad";
51      private static final String RECORD_LAST_DAY_CLASSES = "last_day_classes";
52      private static final String RECORD_LAST_DAY_EXAMS = "last_day_exams";
53      private static final String RECORD_GRADE_SUBMIT_DEADLINE_DATE = "grade_submit_ddln";
54      private static final String RECORD_GRADE_SUBMIT_DEADLINE_TIME = "grade_submit_ddtm";
55  
56      private static final String RECORD_SUB_1_FIRST_DAY = "first_day";
57      private static final String RECORD_SUB_1_CENSUS_DAY = "tenth_day";
58      private static final String RECORD_SUB_1_LAST_DAY_ADD = "last_day_add_a";
59      private static final String RECORD_SUB_1_LAST_DAY_DROP_NOT = "last_drop_not_a";
60      private static final String RECORD_SUB_1_DROP_UG = "last_a_drop_ug";
61      private static final String RECORD_SUB_1_DROP_GRAD = "last_a_drop_grad";
62      private static final String RECORD_SUB_1_LAST_DAY_CLASSES = "last_day_class_a";
63      private static final String RECORD_SUB_1_GRADE_SUBMIT_DEADLINE_DATE = "grade_submit_ddln";
64      private static final String RECORD_SUB_1_GRADE_SUBMIT_DEADLINE_TIME = "grade_submit_ddtm";
65  
66      private static final String RECORD_SUB_2_FIRST_DAY = "first_day_b";
67      private static final String RECORD_SUB_2_LAST_DAY_ADD = "last_day_add_b";
68      private static final String RECORD_SUB_2_LAST_DAY_DROP_NOT = "last_drop_not_b";
69      private static final String RECORD_SUB_2_DROP_UG = "last_b_drop_ug";
70      private static final String RECORD_SUB_2_DROP_GRAD = "last_b_drop_grad";
71      private static final String RECORD_SUB_2_LAST_DAY_CLASSES = "last_day_classes";
72      private static final String RECORD_SUB_2_LAST_DAY_EXAMS = "last_day_exams";
73      private static final String RECORD_SUB_2_GRADE_SUBMIT_DEADLINE_DATE = "grade_submit_ddln";
74      private static final String RECORD_SUB_2_GRADE_SUBMIT_DEADLINE_TIME = "grade_submit_ddtm";
75  
76  
77      private static final String KEYDATE_INSTRUCTIONAL_PERIOD_KEY = "kuali.atp.milestone.InstructionalPeriod";
78      private static final String KEYDATE_FINAL_EXAM_PERIOD_KEY = "kuali.atp.milestone.FinalExamPeriod";
79      private static final String KEYDATE_CENSUS_KEY = "kuali.atp.milestone.FinancialAidCensus";
80      private static final String KEYDATE_DROP_DEADLINE_WITHOUT_RECORD_KEY = "kuali.atp.milestone.DropDeadlineWithoutRecord";
81      private static final String KEYDATE_DROP_DEADLINE_KEY = "kuali.atp.milestone.DropDate";
82      private static final String KEYDATE_GRADES_DUE_KEY = "kuali.atp.milestone.GradesDue";
83      private static final String KEYDATE_COURSE_SELECTION_PERIOD_END_KEY = "kuali.atp.milestone.CourseSelectionPeriodEnd";
84  
85      private static final Map<String, String> keydateNames = new LinkedHashMap<String, String>();
86      {
87          keydateNames.put(KEYDATE_INSTRUCTIONAL_PERIOD_KEY, "Instructional Period");
88          keydateNames.put(KEYDATE_COURSE_SELECTION_PERIOD_END_KEY, "Course Selection Period End");
89          keydateNames.put(KEYDATE_CENSUS_KEY, "Census");
90          keydateNames.put(KEYDATE_DROP_DEADLINE_WITHOUT_RECORD_KEY, "Drop Deadline Without Record");
91          keydateNames.put(KEYDATE_DROP_DEADLINE_KEY, "Drop Deadline");
92          keydateNames.put(KEYDATE_FINAL_EXAM_PERIOD_KEY, "Final Exam Period");
93          keydateNames.put(KEYDATE_GRADES_DUE_KEY, "Grades Due");
94      }
95  
96      private static final Set<String> relativeToInstructionPeriod = new HashSet<String>();
97      {
98          relativeToInstructionPeriod.add(KEYDATE_CENSUS_KEY);
99          relativeToInstructionPeriod.add(KEYDATE_COURSE_SELECTION_PERIOD_END_KEY);
100         relativeToInstructionPeriod.add(KEYDATE_FINAL_EXAM_PERIOD_KEY);
101     }
102 
103     private static final String HOLIDAY_NEWYEARSDAY_KEY = "kuali.atp.milestone.NewYearsDay";
104     private static final String HOLIDAY_MLKDAY_KEY = "kuali.atp.milestone.MLKDay";
105     private static final String HOLIDAY_PRESIDENTSDAY_KEY = "kuali.atp.milestone.PresidentsDay";
106     private static final String HOLIDAY_MEMORIALDAY_KEY = "kuali.atp.milestone.MemorialDay";
107     private static final String HOLIDAY_INDEPENDENCEDAY_KEY = "kuali.atp.milestone.IndependenceDay";
108     private static final String HOLIDAY_LABORDAY_KEY = "kuali.atp.milestone.LaborDay";
109     private static final String HOLIDAY_VETERANSDAY_KEY = "kuali.atp.milestone.VeteransDay";
110     private static final String HOLIDAY_THANKSGIVINGBREAK_KEY = "kuali.atp.milestone.ThanksgivingBreak";
111     private static final String HOLIDAY_CHRISTMAS_KEY = "kuali.atp.milestone.Christmas";
112     private static final String HOLIDAY_OTHERNONINSTRUCTIONALHOLIDAY_KEY = "kuali.atp.milestone.OtherNonInstructionalHoliday";
113     private static final String HOLIDAY_OTHERNONINSTRUCTIONALDAY_KEY = "kuali.atp.milestone.OtherNonInstructionalDay";
114 
115     private static final String HOLIDAY_NEWYEARSDAYOBSERVED_KEY = "kuali.atp.milestone.NewYearsDayObserved";
116     private static final String HOLIDAY_INDEPENDENCEDAYOBSERVED_KEY = "kuali.atp.milestone.IndependenceDayObserved";
117     private static final String HOLIDAY_VETERANSDAYOBSERVED_KEY = "kuali.atp.milestone.VeteransDayObserved";
118     private static final String HOLIDAY_CHRISTMASOBSERVED_KEY = "kuali.atp.milestone.ChristmasObserved";
119 
120 
121     private static final Map<String, String> holidayNames = new LinkedHashMap<String, String>();
122     {
123         holidayNames.put(HOLIDAY_NEWYEARSDAY_KEY, "New Years Day");
124         holidayNames.put(HOLIDAY_MLKDAY_KEY, "Martin Luther King, Jr. Day");
125         holidayNames.put(HOLIDAY_PRESIDENTSDAY_KEY, "Presidents Day");
126         holidayNames.put(HOLIDAY_MEMORIALDAY_KEY, "Memorial Day");
127         holidayNames.put(HOLIDAY_INDEPENDENCEDAY_KEY, "Independence Day");
128         holidayNames.put(HOLIDAY_LABORDAY_KEY, "Labor Day");
129         holidayNames.put(HOLIDAY_VETERANSDAY_KEY, "Veterans Day");
130         holidayNames.put(HOLIDAY_THANKSGIVINGBREAK_KEY, "Thanksgiving Break");
131         holidayNames.put(HOLIDAY_CHRISTMAS_KEY, "Christmas Day");
132         holidayNames.put(HOLIDAY_NEWYEARSDAYOBSERVED_KEY, "New Years Day Observed");
133         holidayNames.put(HOLIDAY_INDEPENDENCEDAYOBSERVED_KEY, "Independence Day Observed");
134         holidayNames.put(HOLIDAY_VETERANSDAYOBSERVED_KEY, "Veterans Day Observed");
135         holidayNames.put(HOLIDAY_CHRISTMASOBSERVED_KEY, "Christmas Day Observed");
136     }
137 
138 
139     private static final Map<String, String> observedHolidays = new LinkedHashMap<String, String>();
140     {
141         observedHolidays.put(HOLIDAY_NEWYEARSDAY_KEY, HOLIDAY_NEWYEARSDAYOBSERVED_KEY);
142         observedHolidays.put(HOLIDAY_MLKDAY_KEY, null);
143         observedHolidays.put(HOLIDAY_PRESIDENTSDAY_KEY, null);
144         observedHolidays.put(HOLIDAY_MEMORIALDAY_KEY, null);
145         observedHolidays.put(HOLIDAY_INDEPENDENCEDAY_KEY, HOLIDAY_INDEPENDENCEDAYOBSERVED_KEY);
146         observedHolidays.put(HOLIDAY_LABORDAY_KEY, null);
147         observedHolidays.put(HOLIDAY_VETERANSDAY_KEY, HOLIDAY_VETERANSDAYOBSERVED_KEY);
148         observedHolidays.put(HOLIDAY_THANKSGIVINGBREAK_KEY, null);
149         observedHolidays.put(HOLIDAY_CHRISTMAS_KEY, HOLIDAY_CHRISTMASOBSERVED_KEY);
150     }
151 
152 
153     public static void main(String[] args) {
154         AcalReferenceDataParser parser = new AcalReferenceDataParser();
155         File inputFile = new File("/kuali/temp/Reference Insitution Data_ Terms - Data.tsv");
156         File outputFile = new File("/kuali/temp/output.sql");
157 
158         parser.loadDataSet(inputFile);
159 
160         int[] records = new int[] {35};
161         for (int record : records) {
162 //            System.out.println("Record " + record + ": " + parser.dataSet.get(record));
163         }
164 
165         parser.parse();
166         SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
167         for (Atp acal : parser.allAcals.values()) {
168             int startYear = Integer.parseInt(yearFormat.format(acal.getStartDate()));
169             int endYear = Integer.parseInt(yearFormat.format(acal.getEndDate()));
170             for (int year = startYear; year <= endYear; year++) {
171                 Atp holiCal = parser.getHolidayCalendar(Integer.toString(year));
172                 acal.getHolidayCalendars().add(holiCal);
173             }
174         }
175 
176         parser.alterEndDates(new ArrayList<Atp>(parser.allAcals.values()));
177 
178 //        System.out.println(parser.getReport(Arrays.asList(new String[]{"1978","2011"})));
179 //        System.out.println(parser.getReport(Arrays.asList(new String[]{"1979"})));
180 
181         SqlGenerator sqlGenerator = new SqlGenerator();
182         StringBuilder sql = new StringBuilder();
183         for (Atp acal : parser.allAcals.values()) {
184             sqlGenerator.getSqlForAcademicCalendar(sql, acal);
185         }
186         parser.writeToFile(outputFile, sql.toString());
187 
188 
189     }
190 
191     private void parse() {
192         int i = 0;
193         for (Map<String, String> record : dataSet) {
194             i++;
195 //            System.out.println("parsing record [" + i + "]");
196             // create term
197             Atp term = createTerm(record);
198 
199             // add term to acal
200             Atp acal = getAcalForTermId(record.get(RECORD_KEY));
201             List<Atp> terms = acal.getTerms();
202             terms.add(term);
203         }
204         for (Atp acal : allAcals.values()) {
205             List<Atp> terms = acal.getTerms();
206             Atp firstTerm = terms.get(0);
207             Atp lastTerm = terms.get(terms.size() - 1);
208             acal.setStartDate(firstTerm.getStartDate());
209             acal.setEndDate(lastTerm.getEndDate());
210         }
211     }
212 
213     private Atp createTerm(Map<String, String> record) {
214         String recordKey = record.get(RECORD_KEY);
215         int year = getYear(recordKey);
216         int quarter = getQuarter(recordKey);
217         String termName = getAtpTypeName(quarter);
218 
219         Atp term = new Atp();
220 
221         term.setId((year + "" + termName).replaceAll(" ", "").toUpperCase());
222         term.setName(termName + " " + year);
223         term.setDescriptionPlain(termName + " " + year);
224         term.setDescriptionFormatted("<p>" + term.getDescriptionPlain() + "</p>");
225         term.setStartDate(parseDate(record.get(RECORD_FIRST_DAY)));
226         Date termEnd = parseDate(record.get(RECORD_LAST_DAY_EXAMS));
227         term.setEndDate(termEnd);
228         term.setType(getAtpTypeKey(quarter));
229         term.setAtpCode(termName.substring(0,3).toUpperCase() + year);
230 
231         for (String keydateType : keydateNames.keySet()) {
232             term.addMilestone(getKeydateForTerm(keydateType, record));
233         }
234 
235         // create subterm 1
236         if (3 == quarter) {
237             String subtermName = SESSION1_ATP_TYPE;
238             Atp subterm = new Atp();
239 
240             subterm.setId((year + "" + subtermName).replaceAll(" ", "").toUpperCase());
241             subterm.setName(subtermName + " " + year);
242             subterm.setDescriptionPlain(subtermName + " " + year);
243             subterm.setDescriptionFormatted("<p>" + subterm.getDescriptionPlain() + "</p>");
244             subterm.setStartDate(parseDate(record.get(RECORD_SUB_1_FIRST_DAY)));
245             Date subTermEnd = parseDate(record.get(RECORD_SUB_1_LAST_DAY_CLASSES));
246             subterm.setEndDate(subTermEnd);
247             subterm.setType(SESSION1_ATP_TYPE_KEY);
248             subterm.setAtpCode(termName.substring(0, 3).toUpperCase() + "A" + year);
249 
250             for (String keydateType : keydateNames.keySet()) {
251                 subterm.addMilestone(getKeydateForSubterm1(keydateType, record));
252             }
253 
254             term.getTerms().add(subterm);
255         }
256 
257 
258 
259         // create subterm 2
260         if (3 == quarter) {
261             String subtermName = SESSION2_ATP_TYPE;
262             Atp subterm = new Atp();
263 
264             subterm.setId((year + "" + subtermName).replaceAll(" ", "").toUpperCase());
265             subterm.setName(subtermName + " " + year);
266             subterm.setDescriptionPlain(subtermName + " " + year);
267             subterm.setDescriptionFormatted("<p>" + subterm.getDescriptionPlain() + "</p>");
268             subterm.setStartDate(parseDate(record.get(RECORD_SUB_2_FIRST_DAY)));
269             Date subTermEnd = parseDate(record.get(RECORD_SUB_2_LAST_DAY_EXAMS));
270             subterm.setEndDate(subTermEnd);
271             subterm.setType(SESSION2_ATP_TYPE_KEY);
272             subterm.setAtpCode(termName.substring(0, 3).toUpperCase() + "A" + year);
273 
274             for (String keydateType : keydateNames.keySet()) {
275                 subterm.addMilestone(getKeydateForSubterm2(keydateType, record));
276             }
277 
278             term.getTerms().add(subterm);
279         }
280 
281         addMilestoneRelations(term);
282         return term;
283     }
284 
285     private Milestone getKeydateForTerm(String keydateType, Map<String, String> record) {
286         String recordKey = record.get(RECORD_KEY);
287         int termYear = getYear(recordKey);
288         String termName = getAtpTypeName(getQuarter(recordKey));
289         String keydateName = keydateNames.get(keydateType);
290 
291         String id = (termYear + termName + keydateName).replaceAll(" ", "").toUpperCase();
292         String name = termName + " " + termYear + " " + keydateName;
293         String descriptionPlain = keydateName + " for " + termName + " " + termYear;
294         String descriptionFormatted = "<p>" + descriptionPlain + "<p>";
295 
296         Date start = null;
297         Date end = null;
298         boolean allDay;
299         boolean range;
300 
301         if (KEYDATE_COURSE_SELECTION_PERIOD_END_KEY.equals(keydateType)) {
302             start = parseDate(record.get(RECORD_LAST_DAY_ADD));
303             if (start == null) {
304 //                throw new RuntimeException(recordKey);
305                 return null; // TODO Some records don't have selection period end. Should this be calculated?
306             }
307             allDay = true;
308             range = false;
309 
310         } else if (KEYDATE_INSTRUCTIONAL_PERIOD_KEY.equals(keydateType)) {
311             start = parseDate(record.get(RECORD_FIRST_DAY));
312             if (start == null) {
313                 throw new RuntimeException(recordKey);
314             }
315             end = parseDate(record.get(RECORD_LAST_DAY_CLASSES));
316             if (end == null) {
317                 throw new RuntimeException(recordKey);
318             }
319             allDay = true;
320             range = true;
321 
322         } else if (KEYDATE_FINAL_EXAM_PERIOD_KEY.equals(keydateType)) {
323             if (3 == getQuarter(record.get(RECORD_KEY))) {
324                 return null;
325             }
326             start = parseDate(record.get(RECORD_LAST_DAY_CLASSES));
327             if (start == null) {
328                 throw new RuntimeException(recordKey);
329             }
330             start = addToDate(start, Calendar.DATE, 1);
331             end = parseDate(record.get(RECORD_LAST_DAY_EXAMS));
332             if (end == null) {
333                 throw new RuntimeException(recordKey);
334             }
335             allDay    = true;
336             range = true;
337 
338         } else if (KEYDATE_CENSUS_KEY.equals(keydateType)) {
339             start = parseDate(record.get(RECORD_CENSUS_DAY));
340             if (start == null) {
341                 throw new RuntimeException(recordKey);
342             }
343             allDay = true;
344             range = false;
345 
346         } else if (KEYDATE_DROP_DEADLINE_WITHOUT_RECORD_KEY.equals(keydateType)) {
347             start = parseDate(record.get(RECORD_LAST_DAY_DROP_NOT));
348             if (start == null) {
349                 throw new RuntimeException(recordKey);
350             }
351             allDay = true;
352             range = false;
353 
354         } else if (KEYDATE_DROP_DEADLINE_KEY.equals(keydateType)) {
355             start = parseDate(record.get(RECORD_DROP_UG));
356             if (start == null) {
357                 throw new RuntimeException(recordKey);
358             }
359             allDay = true;
360             range = false;
361 
362         } else if (KEYDATE_GRADES_DUE_KEY.equals(keydateType)) {
363             start = parseDate(record.get(RECORD_GRADE_SUBMIT_DEADLINE_DATE), record.get(RECORD_GRADE_SUBMIT_DEADLINE_TIME));
364             if (start == null) {
365                 return null;
366             }
367             allDay = false;
368             range = false;
369 
370         } else {
371             throw new RuntimeException(recordKey + " Keydate type not known.");
372         }
373 
374         Milestone milestone = new Milestone(id, name, keydateType, descriptionPlain, descriptionFormatted, start, end, allDay, range);
375         return milestone;
376     }
377 
378     private Milestone getKeydateForSubterm1(String keydateType, Map<String, String> record) {
379         String recordKey = record.get(RECORD_KEY);
380         int termYear = getYear(recordKey);
381         String termName = SESSION1_ATP_TYPE;
382         String keydateName = keydateNames.get(keydateType);
383 
384         String id = (termYear + termName + keydateName).replaceAll(" ", "").toUpperCase();
385         String name = termName + " " + termYear + " " + keydateName;
386         String descriptionPlain = keydateName + " for " + termName + " " + termYear;
387         String descriptionFormatted = "<p>" + descriptionPlain + "<p>";
388 
389         Date start = null;
390         Date end = null;
391         boolean allDay;
392         boolean range;
393 
394         if (KEYDATE_COURSE_SELECTION_PERIOD_END_KEY.equals(keydateType)) {
395             start = parseDate(record.get(RECORD_SUB_1_LAST_DAY_ADD));
396             if (start == null) {
397 //                throw new RuntimeException(recordKey);
398                 return null;  // TODO Some records don't have selection period end
399             }
400             allDay = true;
401             range = false;
402 
403         } else if (KEYDATE_INSTRUCTIONAL_PERIOD_KEY.equals(keydateType)) {
404             start = parseDate(record.get(RECORD_SUB_1_FIRST_DAY));
405             if (start == null) {
406                 throw new RuntimeException(recordKey);
407             }
408             end = parseDate(record.get(RECORD_SUB_1_LAST_DAY_CLASSES));
409             if (end == null) {
410                 end = parseDate(record.get(RECORD_SUB_2_FIRST_DAY));
411                 end = addToDate(end, Calendar.DATE, -1);
412                 if (end == null) {
413                     throw new RuntimeException(record.get(RECORD_KEY));
414                 }
415             }
416             allDay = true;
417             range = true;
418 
419         } else if (KEYDATE_FINAL_EXAM_PERIOD_KEY.equals(keydateType)) {
420             return null;
421 
422         } else if (KEYDATE_CENSUS_KEY.equals(keydateType)) {
423             start = parseDate(record.get(RECORD_SUB_1_CENSUS_DAY));
424             if (start == null) {
425                 throw new RuntimeException(recordKey);
426             }
427             allDay = true;
428             range = false;
429 
430         } else if (KEYDATE_DROP_DEADLINE_WITHOUT_RECORD_KEY.equals(keydateType)) {
431             start = parseDate(record.get(RECORD_SUB_1_LAST_DAY_DROP_NOT));
432             if (start == null) {
433                 throw new RuntimeException(recordKey);
434             }
435             allDay = true;
436             range = false;
437 
438         } else if (KEYDATE_DROP_DEADLINE_KEY.equals(keydateType)) {
439             start = parseDate(record.get(RECORD_SUB_1_DROP_UG));
440             if (start == null) {
441                 throw new RuntimeException(recordKey);
442             }
443             allDay = true;
444             range = false;
445 
446         } else if (KEYDATE_GRADES_DUE_KEY.equals(keydateType)) {
447             start = parseDate(record.get(RECORD_SUB_1_GRADE_SUBMIT_DEADLINE_DATE), record.get(RECORD_SUB_1_GRADE_SUBMIT_DEADLINE_TIME));
448             if (start == null) {
449                 return null;
450             }
451             allDay = false;
452             range = false;
453 
454         } else {
455             throw new RuntimeException(recordKey + " Keydate type not known.");
456         }
457 
458         Milestone milestone = new Milestone(id, name, keydateType, descriptionPlain, descriptionFormatted, start, end, allDay, range);
459         return milestone;
460     }
461 
462     private Milestone getKeydateForSubterm2(String keydateType, Map<String, String> record) {
463         String recordKey = record.get(RECORD_KEY);
464         int termYear = getYear(recordKey);
465         String termName = SESSION2_ATP_TYPE;
466         String keydateName = keydateNames.get(keydateType);
467 
468         String id = (termYear + termName + keydateName).replaceAll(" ", "").toUpperCase();
469         String name = termName + " " + termYear + " " + keydateName;
470         String descriptionPlain = keydateName + " for " + termName + " " + termYear;
471         String descriptionFormatted = "<p>" + descriptionPlain + "<p>";
472 
473         Date start = null;
474         Date end = null;
475         boolean allDay;
476         boolean range;
477 
478         if (KEYDATE_COURSE_SELECTION_PERIOD_END_KEY.equals(keydateType)) {
479             start = parseDate(record.get(RECORD_SUB_2_LAST_DAY_ADD));
480             if (start == null) {
481 //                throw new RuntimeException(recordKey);
482                 return null; // TODO Some records don't have selection period end
483             }
484             allDay = true;
485             range = false;
486 
487         } else if (KEYDATE_INSTRUCTIONAL_PERIOD_KEY.equals(keydateType)) {
488             start = parseDate(record.get(RECORD_SUB_2_FIRST_DAY));
489             if (start == null) {
490                 throw new RuntimeException(recordKey);
491             }
492             end = parseDate(record.get(RECORD_SUB_2_LAST_DAY_CLASSES));
493             if (end == null) {
494                 throw new RuntimeException(recordKey);
495             }
496             allDay = true;
497             range = true;
498 
499         } else if (KEYDATE_FINAL_EXAM_PERIOD_KEY.equals(keydateType)) {
500             return null;
501 
502         } else if (KEYDATE_CENSUS_KEY.equals(keydateType)) {
503             return null;
504 
505         } else if (KEYDATE_DROP_DEADLINE_WITHOUT_RECORD_KEY.equals(keydateType)) {
506             start = parseDate(record.get(RECORD_SUB_2_LAST_DAY_DROP_NOT));
507             if (start == null) {
508                 throw new RuntimeException(recordKey);
509             }
510             allDay = true;
511             range = false;
512 
513         } else if (KEYDATE_DROP_DEADLINE_KEY.equals(keydateType)) {
514             start = parseDate(record.get(RECORD_SUB_2_DROP_UG));
515             if (start == null) {
516                 throw new RuntimeException(recordKey);
517             }
518             allDay = true;
519             range = false;
520 
521         } else if (KEYDATE_GRADES_DUE_KEY.equals(keydateType)) {
522             start = parseDate(record.get(RECORD_SUB_2_GRADE_SUBMIT_DEADLINE_DATE), record.get(RECORD_SUB_2_GRADE_SUBMIT_DEADLINE_TIME));
523             if (start == null) {
524                 return null;
525             }
526             allDay = false;
527             range = false;
528 
529         } else {
530             throw new RuntimeException("Keydate type not known.");
531         }
532 
533         Milestone milestone = new Milestone(id, name, keydateType, descriptionPlain, descriptionFormatted, start, end, allDay, range);
534         return milestone;
535     }
536 
537     private Date addToDate(Date date, int field, int amount) {
538         if (date == null) {
539             return null;
540         }
541         Calendar cal = new GregorianCalendar();
542         cal.setTime(date);
543         cal.add(field, amount);
544         return cal.getTime();
545     }
546     private Date parseDate(String dateString) {
547         Date date = null;
548         if (dateString != null && dateString.length() > 0) {
549             String[] dateSegments = dateString.split("/");
550             int day = Integer.parseInt(dateSegments[1]);
551             int month = Integer.parseInt(dateSegments[0]) - 1;
552             int year = Integer.parseInt(dateSegments[2]);
553             if (year > 99) {
554                 throw new RuntimeException("Unexpected year format: " + dateString);
555             }
556             if (year > 20) {
557                 year = 1900 + year;
558             } else {
559                 year = 2000 + year;
560             }
561             Calendar cal = new GregorianCalendar(year, month, day);
562             date = cal.getTime();
563         }
564         return date;
565     }
566 
567     private Date parseDate(String dateString, String timeString) {
568         Date date = null;
569         if (dateString != null && dateString.length() > 0 && timeString != null && timeString.length() > 0) {
570             while (timeString.length() < 6) {
571                 timeString = "0" + timeString;
572             }
573 
574             int hour = Integer.parseInt(timeString.substring(0,2));
575             int minute = Integer.parseInt(timeString.substring(2,4));
576             int second = Integer.parseInt(timeString.substring(4,6));
577 
578             Calendar cal = new GregorianCalendar();
579             cal.setTime(parseDate(dateString));
580             cal.set(Calendar.HOUR_OF_DAY, hour);
581             cal.set(Calendar.MINUTE, minute);
582             cal.set(Calendar.SECOND, second);
583             date = cal.getTime();
584         }
585         return date;
586     }
587 
588     int getQuarter(String recordKey) {
589         if (recordKey.length() != 5) {
590             throw new IllegalArgumentException("recordKey: " + recordKey);
591         }
592         int quater = Integer.parseInt(recordKey.substring(4, 5));
593         return quater;
594     }
595     int getYear(String recordKey) {
596         if (recordKey.length() != 5) {
597             throw new IllegalArgumentException("recordKey: " + recordKey);
598         }
599         int year = Integer.parseInt(recordKey.substring(0, 4));
600         return year;
601     }
602     String getAtpTypeName(int quarter) {
603         String name = null;
604         switch (quarter) {
605             case 1: name = WINTER_ATP_TYPE; break;
606             case 2: name = SPRING_ATP_TYPE; break;
607             case 3: name = SUMMER_ATP_TYPE; break;
608             case 4: name = FALL_ATP_TYPE; break;
609         }
610         return name;
611     }
612     String getAtpTypeKey(int quarter) {
613         String name = null;
614         switch (quarter) {
615             case 1: name = WINTER_ATP_TYPE_KEY; break;
616             case 2: name = SPRING_ATP_TYPE_KEY; break;
617             case 3: name = SUMMER_ATP_TYPE_KEY; break;
618             case 4: name = FALL_ATP_TYPE_KEY; break;
619         }
620         return name;
621     }
622 
623     private Atp getAcalForTermId(String termId) {
624         if (termId.length() != 5) {
625             throw new IllegalArgumentException("termId: " + termId);
626         }
627         int year = getYear(termId);
628         int quater = getQuarter(termId);
629         if (quater < 3) {
630             year--;
631         }
632         Atp acal = allAcals.get(String.valueOf(year));
633         if (acal == null) {
634             acal = new Atp();
635             allAcals.put(String.valueOf(year), acal);
636             acal.setId(year + "" + (year + 1) + "ACADEMICCALENDAR");
637             acal.setType("kuali.atp.type.AcademicCalendar");
638             acal.setAdminOrgId("102"); // Registrar code
639             acal.setAtpCode("ACAL"+year);
640             acal.setName(year + "/" + (year + 1) + " Academic Calendar");
641             acal.setDescriptionPlain("Academic Calendar for " + year + "/" + (year + 1));
642             acal.setDescriptionFormatted("<p>" + acal.getDescriptionPlain() + "</p>");
643         }
644         return acal;
645     }
646 
647     private void loadDataSet(File file) {
648         DataInputStream in = null;
649         try {
650             FileInputStream fstream = new FileInputStream(file);
651             in = new DataInputStream(fstream);
652             BufferedReader br = new BufferedReader(new InputStreamReader(in));
653 
654             String[] headers = null;
655             String line;
656             int row = 0;
657             while ((line = br.readLine()) != null) {
658                 String[] columnValues = line.split("\t");
659                 if (0 == row) {
660                     headers = columnValues;
661                 } else {
662                     Map<String, String> map = new LinkedHashMap<String, String>();
663                     for (int i = 0; i < headers.length; i++) {
664                         map.put(headers[i], columnValues[i]);
665                     }
666                     dataSet.add(map);
667                 }
668                 row++;
669             }
670         } catch (Exception e) {
671             e.printStackTrace();
672         } finally {
673             if (in != null) {
674                 try {
675                     in.close();
676                 } catch (IOException e) {
677                     e.printStackTrace();
678                 }
679             }
680         }
681     }
682 
683     private void writeToFile(File file, String content) {
684         try{
685             FileWriter fstream = new FileWriter(file);
686             BufferedWriter out = new BufferedWriter(fstream);
687             out.write(content);
688             out.close();
689         } catch (Exception e){
690             e.printStackTrace();
691         }
692     }
693 
694     public String getReport(List<String> years) {
695         StringBuilder builder = new StringBuilder();
696         String tab = "\t";
697         String line = "\n";
698 
699 
700         for (String year : years) {
701             Atp acal = allAcals.get(year);
702             addReportLineForAtp(builder, acal);
703             for (Atp holiCal : acal.getHolidayCalendars()) {
704                 addReportLineForAtp(builder, holiCal);
705                 for (Milestone holiday : holiCal.getMilestones()) {
706                     addReportLineForMilestone(builder, holiday);
707                 }
708             }
709             for (Atp term : acal.getTerms()) {
710                 addReportLineForAtp(builder, term);
711                 for (Milestone milestone : term.getMilestones()) {
712                     addReportLineForMilestone(builder, milestone);
713                 }
714                 for (Atp subTerm : term.getTerms()) {
715                     addReportLineForAtp(builder, subTerm);
716                     for (Milestone milestone : subTerm.getMilestones()) {
717                         addReportLineForMilestone(builder, milestone);
718                     }
719 
720                 }
721             }
722         }
723         return builder.toString();
724     }
725 
726 
727     private StringBuilder addReportLineForAtp(StringBuilder builder, Atp atp) {
728         String tab = "\t";
729         String line = "\n";
730 
731         if (builder == null) {
732             builder = new StringBuilder();
733         }
734 
735         builder.append(atp.getName()).append(tab);
736 
737         Date startDate = atp.getStartDate();
738         builder.append(getDate(startDate)).append(tab);
739         builder.append(tab);
740 
741         Date endDate = atp.getEndDate();
742         builder.append(getDate(endDate)).append(tab);
743         builder.append(tab);
744 
745         builder.append(atp.getType());
746 
747         builder.append(line);
748         return builder;
749     }
750 
751     private StringBuilder addReportLineForMilestone(StringBuilder builder, Milestone milestone) {
752         String tab = "\t";
753         String line = "\n";
754 
755         if (builder == null) {
756             builder = new StringBuilder();
757         }
758 
759         builder.append(milestone.getName()).append(tab);
760 
761         Date startDate = milestone.getStartDate();
762         builder.append(getDate(startDate)).append(tab);
763         if (!milestone.isAllDay()) {
764             builder.append(getTime(startDate));
765         }
766         builder.append(tab);
767 
768         Date endDate = milestone.getEndDate();
769         if (milestone.isDateRange()) {
770             if (endDate == null) {
771                 throw new RuntimeException("End date not supplied for range. " + milestone.getId());
772             }
773             builder.append(getDate(endDate)).append(tab);
774             if (!milestone.isAllDay()) {
775                 builder.append(getTime(endDate));
776             }
777         } else {
778             builder.append(tab);
779             if (endDate != null) {
780                 throw new RuntimeException("End date not expected for non range. " + milestone.getId());
781             }
782         }
783         builder.append(tab);
784         builder.append(milestone.getType());
785 
786         builder.append(line);
787         return builder;
788     }
789 
790     private String getDate(Date date) {
791         SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MMM-yyyy");
792         if (date == null) {
793             return "";
794         }
795         return dateFormat.format(date);
796     }
797     private String getTime(Date date) {
798         SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss");
799         return timeFormat.format(date);
800     }
801 
802     public Atp getHolidayCalendar(String yearString) {
803         Atp holiCal = allHolidayCalendars.get(yearString);
804         int year = Integer.parseInt(yearString);
805         if (null == holiCal) {
806             holiCal = new Atp();
807             holiCal.setId(year + "HOLIDAYCALENDAR");
808             holiCal.setName(year + " Holiday Calendar");
809             holiCal.setStartDate(new GregorianCalendar(year, Calendar.JANUARY, 1).getTime());
810             holiCal.setEndDate(new GregorianCalendar(year, Calendar.DECEMBER, 31).getTime());
811             holiCal.setDescriptionPlain("Holiday Calendar for " + year);
812             holiCal.setDescriptionFormatted("<p>" + holiCal.getDescriptionPlain() + "</p>");
813             holiCal.setType("kuali.atp.type.HolidayCalendar");
814             holiCal.setAtpCode("HCAL" + year);
815             holiCal.setAdminOrgId("102"); // Registrar code
816 
817             for (String holidayTypeKey : observedHolidays.keySet()) {
818                 Milestone holiday = getHoliday(holidayTypeKey, year);
819                 holiCal.addMilestone(holiday);
820                 Milestone observedholiday = getHoliday(observedHolidays.get(holidayTypeKey), year);
821                 if (null != observedholiday) {
822                     holiCal.addMilestone(observedholiday);
823                 }
824             }
825             allHolidayCalendars.put(yearString, holiCal);
826         }
827         return holiCal;
828     }
829 
830     public Milestone getHoliday(String holidayType, int year) {
831         if (null == holidayType) {
832             return null;
833         }
834 
835         String[] typeSplit = holidayType.split("\\.");
836 
837         String id = (year + typeSplit[typeSplit.length-1]).toUpperCase();
838         String name = holidayNames.get(holidayType);
839         String descriptionPlain = name + " for " + year;
840         String descriptionFormatted = "<p>" + descriptionPlain + "<p>";
841 
842         Date start = null;
843         Date end = null;
844         boolean allDay = true;
845         boolean range = false;
846 
847         if (HOLIDAY_NEWYEARSDAY_KEY.equals(holidayType)) {
848             Calendar cal = new GregorianCalendar(year, Calendar.JANUARY, 1);
849             start = cal.getTime();
850         } else if (HOLIDAY_NEWYEARSDAYOBSERVED_KEY.equals(holidayType)) {
851             Milestone milestone = getHoliday(HOLIDAY_NEWYEARSDAY_KEY, year);
852             int offset = getWeekendOffset(milestone.getStartDate());
853             if (0 == offset) {
854                 return null;
855             } else {
856                 start = addToDate(milestone.getStartDate(), Calendar.DATE, offset);
857                 end = addToDate(milestone.getEndDate(), Calendar.DATE, offset);
858             }
859         } else if (HOLIDAY_MLKDAY_KEY.equals(holidayType)) {
860             start = getNthDayOfWeekInMonth(3, Calendar.MONDAY, Calendar.JANUARY, year);
861         } else if (HOLIDAY_PRESIDENTSDAY_KEY.equals(holidayType)) {
862             start = getNthDayOfWeekInMonth(3, Calendar.MONDAY, Calendar.FEBRUARY, year);
863         } else if (HOLIDAY_MEMORIALDAY_KEY.equals(holidayType)) {
864             start = getNthDayOfWeekInMonth(1, Calendar.MONDAY, Calendar.JUNE, year);
865             start = addToDate(start, Calendar.DATE, -7);
866         } else if (HOLIDAY_INDEPENDENCEDAY_KEY.equals(holidayType)) {
867             start = new GregorianCalendar(year, Calendar.JULY, 4).getTime();
868         } else if (HOLIDAY_INDEPENDENCEDAYOBSERVED_KEY.equals(holidayType)) {
869             Milestone milestone = getHoliday(HOLIDAY_INDEPENDENCEDAY_KEY, year);
870             int offset = getWeekendOffset(milestone.getStartDate());
871             if (0 == offset) {
872                 return null;
873             } else {
874                 start = addToDate(milestone.getStartDate(), Calendar.DATE, offset);
875                 end = addToDate(milestone.getEndDate(), Calendar.DATE, offset);
876             }
877         } else if (HOLIDAY_LABORDAY_KEY.equals(holidayType)) {
878             start = getNthDayOfWeekInMonth(1, Calendar.MONDAY, Calendar.SEPTEMBER, year);
879         } else if (HOLIDAY_VETERANSDAY_KEY.equals(holidayType)) {
880             start = new GregorianCalendar(year, Calendar.NOVEMBER, 11).getTime();
881         } else if (HOLIDAY_VETERANSDAYOBSERVED_KEY.equals(holidayType)) {
882             Milestone milestone = getHoliday(HOLIDAY_VETERANSDAY_KEY, year);
883             int offset = getWeekendOffset(milestone.getStartDate());
884             if (0 == offset) {
885                 return null;
886             } else {
887                 start = addToDate(milestone.getStartDate(), Calendar.DATE, offset);
888                 end = addToDate(milestone.getEndDate(), Calendar.DATE, offset);
889             }
890         } else if (HOLIDAY_THANKSGIVINGBREAK_KEY.equals(holidayType)) {
891             start = getNthDayOfWeekInMonth(4, Calendar.THURSDAY, Calendar.NOVEMBER, year);
892             end = addToDate(start, Calendar.DATE, 1);
893             range = true;
894         } else if (HOLIDAY_CHRISTMAS_KEY.equals(holidayType)) {
895             start = new GregorianCalendar(year, Calendar.DECEMBER, 25).getTime();
896         } else if (HOLIDAY_CHRISTMASOBSERVED_KEY.equals(holidayType)) {
897             Milestone milestone = getHoliday(HOLIDAY_CHRISTMAS_KEY, year);
898             int offset = getWeekendOffset(milestone.getStartDate());
899             if (0 == offset) {
900                 return null;
901             } else {
902                 start = addToDate(milestone.getStartDate(), Calendar.DATE, offset);
903                 end = addToDate(milestone.getEndDate(), Calendar.DATE, offset);
904             }
905         } else {
906             throw new RuntimeException("Holiday type not known. " + holidayType);
907         }
908 
909         Milestone milestone = new Milestone(id, name, holidayType, descriptionPlain, descriptionFormatted, start, end, allDay, range);
910         return milestone;
911     }
912 
913     private void addMilestoneRelations(Atp term) {
914         List<Milestone> keyDates = term.getMilestones();
915         Milestone instructionperiod = null;
916         for (Milestone keyDate : keyDates) {
917             if (KEYDATE_INSTRUCTIONAL_PERIOD_KEY.equals(keyDate.getType())) {
918                 instructionperiod = keyDate;
919                 break;
920             }
921         }
922         if (null == instructionperiod) {
923             throw new RuntimeException("Could not find instruction period for term: " + term.getId());
924         }
925         for (Milestone keyDate : keyDates) {
926             if (relativeToInstructionPeriod.contains(keyDate.getType())) {
927                 keyDate.setRelative(true);
928                 keyDate.setRelativeMilestoneId(instructionperiod.getId());
929             } else {
930                 keyDate.setRelative(false);
931             }
932         }
933 
934         for (Atp atp : term.getTerms()) {
935             addMilestoneRelations(atp);
936         }
937     }
938 
939     private void alterEndDates(List<Atp> acals) {
940         for (int i = acals.size()-1; i >= 0 ; i--) {
941             Atp acal = acals.get(i);
942             if (i > 0) {
943                 Atp prevAcal = acals.get(i-1);
944                 Date start = acal.getStartDate();
945                 Date prevEndDate = addToDate(start, Calendar.DATE, -1);
946                 prevAcal.setEndDate(prevEndDate);
947                 List<Atp> prevTerms = prevAcal.getTerms();
948                 if (!prevTerms.isEmpty()) {
949                     Atp lastPrevTerm = prevTerms.get(prevTerms.size()-1);
950                     lastPrevTerm.setEndDate(prevEndDate);
951                     List<Atp> prevSubTerms = lastPrevTerm.getTerms();
952                     if (!prevSubTerms.isEmpty()) {
953                         Atp lastPrevTermSubTerm = prevSubTerms.get(prevSubTerms.size()-1);
954                         lastPrevTermSubTerm.setEndDate(prevEndDate);
955                     }
956                 }
957             }
958 
959             List<Atp> terms = acal.getTerms();
960             for (int j = terms.size()-1; j >= 0; j--) {
961                 Atp term = terms.get(j);
962                 if (j > 0) {
963                     Atp prevTerm = terms.get(j-1);
964                     Date prevEndDate = addToDate(term.getStartDate(), Calendar.DATE, -1);
965                     prevTerm.setEndDate(prevEndDate);
966                     List<Atp> prevSubTerms = prevTerm.getTerms();
967                     if (!prevSubTerms.isEmpty()) {
968                         Atp lastPrevSubTerm = prevSubTerms.get(prevSubTerms.size()-1);
969                         lastPrevSubTerm.setEndDate(prevEndDate);
970                     }
971                 }
972             }
973 
974             for (int j = terms.size()-1; j >= 0; j--) {
975                 Atp term = terms.get(j);
976                 if (j > 0) {
977                     Atp prevTerm = terms.get(j-1);
978                     Date start = term.getStartDate();
979                     prevTerm.setEndDate(addToDate(start, Calendar.DATE, -1));
980                 }
981                 List<Atp> subTerms = term.getTerms();
982                 for (int k = subTerms.size()-1; k >= 0; k--) {
983                     Atp subTerm = subTerms.get(k);
984                     if (k > 0) {
985                         Atp prevSubTerm = subTerms.get(k-1);
986                         Date start = subTerm.getStartDate();
987                         prevSubTerm.setEndDate(addToDate(start, Calendar.DATE, -1));
988                     }
989                 }
990             }
991         }
992     }
993 
994     private int getWeekendOffset(Date date) {
995         Calendar cal = new GregorianCalendar();
996         cal.setTime(date);
997         int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
998         if (Calendar.SATURDAY == dayOfWeek) {
999             return -1;
1000         } else if (Calendar.SUNDAY == dayOfWeek) {
1001             return 1;
1002         } else {
1003             return 0;
1004         }
1005     }
1006 
1007     private Date getNthDayOfWeekInMonth(int n, int dayOfWeek, int month, int year) {
1008         Calendar cal = new GregorianCalendar(year, month, 1);
1009         if (dayOfWeek != cal.get(Calendar.DAY_OF_WEEK)) {
1010             int daysUntil = (dayOfWeek - cal.get(Calendar.DAY_OF_WEEK) + 7) % 7;
1011             cal.add(Calendar.DATE, daysUntil);
1012         }
1013         cal.add(Calendar.DATE, (n-1) * 7);
1014         return cal.getTime();
1015     }
1016 
1017 }