View Javadoc

1   /**
2    * Copyright 2012 The Kuali Foundation Licensed under the
3    * Educational Community License, Version 2.0 (the "License"); you may
4    * not use this file except in compliance with the License. You may
5    * obtain a copy of the License at
6    *
7    * http://www.osedu.org/licenses/ECL-2.0
8    *
9    * Unless required by applicable law or agreed to in writing,
10   * software distributed under the License is distributed on an "AS IS"
11   * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12   * or implied. See the License for the specific language governing
13   * permissions and limitations under the License.
14   *
15   * Created by Charles on 11/14/12
16   */
17  package org.kuali.student.enrollment.class2.courseoffering.refdata;
18  
19  import org.apache.log4j.Logger;
20  import org.kuali.student.enrollment.courseoffering.dto.ActivityOfferingInfo;
21  import org.kuali.student.enrollment.courseoffering.dto.CourseOfferingInfo;
22  import org.kuali.student.enrollment.courseoffering.dto.FormatOfferingInfo;
23  import org.kuali.student.enrollment.courseoffering.service.CourseOfferingService;
24  import org.kuali.student.r2.common.dto.AmountInfo;
25  import org.kuali.student.r2.common.dto.AttributeInfo;
26  import org.kuali.student.r2.common.dto.ContextInfo;
27  import org.kuali.student.r2.common.dto.StatusInfo;
28  import org.kuali.student.r2.common.exceptions.AlreadyExistsException;
29  import org.kuali.student.r2.common.exceptions.CircularReferenceException;
30  import org.kuali.student.r2.common.exceptions.CircularRelationshipException;
31  import org.kuali.student.r2.common.exceptions.DataValidationErrorException;
32  import org.kuali.student.r2.common.exceptions.DependentObjectsExistException;
33  import org.kuali.student.r2.common.exceptions.DoesNotExistException;
34  import org.kuali.student.r2.common.exceptions.InvalidParameterException;
35  import org.kuali.student.r2.common.exceptions.MissingParameterException;
36  import org.kuali.student.r2.common.exceptions.OperationFailedException;
37  import org.kuali.student.r2.common.exceptions.PermissionDeniedException;
38  import org.kuali.student.r2.common.exceptions.ReadOnlyException;
39  import org.kuali.student.r2.common.exceptions.UnsupportedActionException;
40  import org.kuali.student.r2.common.exceptions.VersionMismatchException;
41  import org.kuali.student.r2.lum.course.dto.ActivityInfo;
42  import org.kuali.student.r2.lum.course.dto.CourseInfo;
43  import org.kuali.student.r2.lum.course.dto.FormatInfo;
44  import org.kuali.student.r2.lum.course.infc.Course;
45  import org.kuali.student.r2.lum.course.service.CourseService;
46  import org.kuali.student.r2.lum.lrc.service.LRCService;
47  import org.springframework.transaction.annotation.Propagation;
48  import org.springframework.transaction.annotation.Transactional;
49  
50  import java.io.File;
51  import java.io.FileNotFoundException;
52  import java.io.FileOutputStream;
53  import java.io.IOException;
54  import java.io.PrintStream;
55  import java.util.ArrayList;
56  import java.util.Arrays;
57  import java.util.Collections;
58  import java.util.HashMap;
59  import java.util.HashSet;
60  import java.util.List;
61  import java.util.Map;
62  import java.util.Scanner;
63  import java.util.Set;
64  import java.util.TreeMap;
65  import java.util.regex.Pattern;
66  
67  /**
68   * 1. Remove any Course with no Course Offerings
69   * 2. Remove any Activity with 0 contact hours (they don't exist)
70   *
71   * @author Kuali Student Team
72   */
73  public class CluFixerImpl implements CluFixer {
74      private CourseOfferingService coService;
75      private CourseService courseService;
76      private LRCService lrcService;
77      private static final Logger LOGGER = Logger.getLogger(CluFixer.class);
78      private String pathPrefix = "";
79  
80      private ContextInfo context = new ContextInfo();
81  
82      private static final String VALID_COURSE_CODES = "validCourseCodes.txt";
83      private static final String COURSE_IDS = "courseIds.txt";
84      private static final String BSCI105_COURSE_ID = "1c0fe33d-2233-4659-8a06-3adc8f6a17e1";
85  
86      public void setCoService(CourseOfferingService coService) {
87          this.coService = coService;
88      }
89  
90      public void setCourseService(CourseService courseService) {
91          this.courseService = courseService;
92      }
93  
94      public void setLrcService(LRCService lrcService) {
95          this.lrcService = lrcService;
96      }
97  
98      public void setPathPrefix(String prefix) {
99          pathPrefix = prefix;
100     }
101     /**
102      *
103      * @param filePath Path to a file (e.g. "C:/Users/Charles/Desktop/Kuali/RefData/courseIds.txt")
104      *                 which contains list of Course IDs, one per line;
105      */
106     public void cleanClus(String filePath) throws Exception {
107         context.setAuthenticatedPrincipalId("REF_DATA_BATCH");
108         context.setPrincipalId("REF_DATA_BATCH");
109 
110         setPathPrefix("C:/work/ws/ks/enrollment/aggregate-umd-enr-data/ks-enroll/ks-enroll-impl/src/main/java/org/kuali/student/enrollment/class2/courseoffering/refdata/datafiles/");
111         _deleteUnusedCoursesDefault();
112         _modifyFormatsForCourses();
113 
114 
115     }
116 
117     private void _sortFile(String inputFileName) throws IOException {
118         String truncatedFilename = inputFileName;
119         if (inputFileName.endsWith(".txt")) {
120             truncatedFilename = inputFileName.substring(0, inputFileName.length() - 4);
121         }
122         String newOutputFilename = truncatedFilename + ".sorted.txt";
123         List<String> list = _loadDataFromFile(inputFileName);
124         Collections.sort(list);
125         _writeInfoToFile(list, newOutputFilename);
126     }
127 
128     private void _filterCourseIdsToValidCourseIds(String fileName) throws IOException {
129         LOGGER.info(" START ===============================================");
130         List<String> results = new ArrayList<String>();
131         List<String> origCourseIds = _loadDataFromFile(fileName); // "courseIds.txt"
132         Set<String> validCourseCodes = new HashSet<String>(_loadDataFromFile("validCourseCodes.txt"));
133         Set<String> exemptCourseCodes = new HashSet<String>(_loadDataFromFile("exemptBadCourseCodes.txt"));
134         PrintStream skipOut = _createPrintStream("skippedCourseIds.txt");
135         int count = 0;
136         for (String courseId: origCourseIds) {
137             try {
138                 if (count > 0 && count % 25 == 0) {
139                     System.err.println("count = " + count);
140                     skipOut.flush();
141                 }
142                 count++;
143                 CourseInfo info = courseService.getCourse(courseId, context);
144                 results.add(info.getCode() + "/" + info.getId());
145             } catch (DoesNotExistException e) {
146                 LOGGER.warn("DoesNotExist: Skipping " + courseId);
147                 skipOut.println(courseId);
148             } catch (Exception e) {
149                 LOGGER.warn("OtherException (" + e.getClass().toString() + "): Skipping " + courseId);
150                 skipOut.println("(" + e.getClass().toString() + ") " + courseId);
151             }
152         }
153         skipOut.close();
154         PrintStream pOut = _createPrintStream("validCourseCodeAndCourseIds.txt");
155         for (String line: results) {
156             pOut.println(line);
157         }
158         pOut.close();
159         LOGGER.info(" END   ===============================================");
160     }
161 
162     private void _modifyFormatsForCourses() throws
163             InvalidParameterException, MissingParameterException, DoesNotExistException,
164             PermissionDeniedException, OperationFailedException, DataValidationErrorException,
165             DependentObjectsExistException, UnsupportedActionException, AlreadyExistsException,
166             CircularRelationshipException, CircularReferenceException, ReadOnlyException, VersionMismatchException {
167 
168         int firstActivityType = 0;
169         int secondActivityType = 1;
170 
171         List<Map<String, List<String>>> listOfMaps = _getCluLuiActivityTypes("cluLuiActivityTypes.txt");
172             Map<String, List<String>> shortNameToCluLuiTypes = listOfMaps.get(firstActivityType);
173             Map<String, List<String>> cluTypeToShortNameLuiTypeList = listOfMaps.get(secondActivityType);
174         Map<String, List<String>> courseCodeToFormatList = _loadCourseCodeToFormatList("courseCodeToFormats.txt");
175         // Now to get a list of course IDs
176         List<String> courseIdsPlus = _loadDataFromFile("validCourseCodeAndCourseIds.sorted.txt");
177         List<String> courseIds = new ArrayList<String>();
178         Map<String, String> courseIdToCourseCode = new HashMap<String, String>();
179         for (String line: courseIdsPlus) {
180             String[] parts = line.split("/");
181             courseIds.add(parts[1]); // Extract out the IDs
182             courseIdToCourseCode.put(parts[1], parts[0]);
183         }
184 
185 //        String courseId = "CLUID-BSCI288-199908000000";
186 //        String courseId = "86c94194-062d-4a49-a9d2-ef24de44d860";
187 //        String courseId = "d2ac6c8d-53e4-4eab-9095-d128f9d433a1";
188 //        String courseId = "b981491a-97e9-4d53-8a7c-189ebedc0028";
189         for (String courseId: courseIds) {
190             System.err.println("--------------- Processing " + courseIdToCourseCode.get(courseId) + " (" + courseId +")");
191             _modifyFormatsForCourse(courseId, courseCodeToFormatList, shortNameToCluLuiTypes, cluTypeToShortNameLuiTypeList);
192         }
193         System.err.println("=========== Finished modifying formats");
194     }
195 
196     private void _filterLog(String inputFileName) throws IOException {
197        List<String> result = _loadDataFromFile(inputFileName, new BasicFilter() {
198            @Override
199            public boolean permits(String input) {
200                return input.indexOf("Caused by: org.kuali.student.r2.common.exceptions.DoesNotExistException:") >= 0;
201            }
202        });
203         _writeInfoToFile(result, inputFileName + ".filtered.txt");
204     }
205 
206     private void _deleteUnusedCoursesDefault() {
207         _deleteUnusedCourses("badCourseInfo.txt", "exemptBadCourseCodes.txt");
208     }
209 
210     private void _deleteUnusedCourses(String badCourseInfoFilename, String exemptBadCourseCodesFilename) {
211         // badCourseInfo.txt FORMAT: <course code>/<clu id>/<version ind id>
212         // exemptBadCourseCodes.txt FORMAT: <course code>
213         Map<String, String> courseIdToCourseCode = _computeBadCourseInfoMap(badCourseInfoFilename);
214         Set<String> badCourseCodesSet = _computeExemptBadCourseCodes(exemptBadCourseCodesFilename);
215         for (Map.Entry<String, String> entry: courseIdToCourseCode.entrySet()) {
216             if (badCourseCodesSet.contains(entry.getValue())) {
217                 LOGGER.info("Skipping " + entry.getValue() + " (" + entry.getKey() + ")");
218                 continue; // Skip to next one
219             }
220             // Otherwise, we're here
221             StatusInfo statusInfo = null;
222             try {
223                 statusInfo = courseService.deleteCourse(entry.getKey(), context);
224                 LOGGER.info("Deleted " + entry.getValue() + " (" + entry.getKey() + ")");
225             } catch (Exception e) {
226                 LOGGER.warn("Failed to delete " + entry.getValue() + " (" + entry.getKey() + ")");
227                 continue;
228             }
229         }
230     }
231 
232     private Map<String,String> _computeBadCourseInfoMap(String badCourseInfoFilename) {
233         Map<String,String> courseIdToCourseCode = new HashMap<String, String>();
234         List<String> lines = _loadDataFromFile(badCourseInfoFilename);
235         int firstDataFromFile = 0;
236         int secondDataFromFile = 1;
237 
238         for (String line: lines) {
239             // Each line is a course code, course ID, and version independent ID separated by /
240             List<String> parts = Arrays.asList(line.split("/"));
241             //Code Modified for JIRA 8727
242             if(!parts.isEmpty() && parts.size()>1){
243                 courseIdToCourseCode.put(parts.get(secondDataFromFile), parts.get(firstDataFromFile));
244             }
245 
246         }
247         return courseIdToCourseCode;
248     }
249 
250     private Set<String> _computeExemptBadCourseCodes(String exemptBadCourseCodesFilename) {
251         List<String> badCourseCodes = _loadDataFromFile(exemptBadCourseCodesFilename);
252         Set<String> badCourseCodesSet = new HashSet<String>(badCourseCodes);
253         return badCourseCodesSet;
254     }
255 
256     private List<String> _inferFormat(Map<String, List<String>> cluTypeToShortNameLuiTypeList, FormatInfo firstFormat) {
257         List<String> formatList;
258         List<ActivityInfo> activityInfos = firstFormat.getActivities();
259         String format = null;
260         for (ActivityInfo activity: activityInfos) {
261             if (activity.getContactHours() != null && activity.getContactHours().getUnitQuantity() != null
262                     && Integer.parseInt(activity.getContactHours().getUnitQuantity()) > 0) {
263                 // Check that there are contact hours (often it's null when there are none).
264                 String activityType = activity.getTypeKey();
265                 int firstLuiType= 0;
266                 String shortName = cluTypeToShortNameLuiTypeList.get(activityType).get(firstLuiType);
267                 if (format == null) {
268                     format = shortName;
269                 } else {
270                     format += "/" + shortName;
271                 }
272             }
273         }
274         formatList = new ArrayList<String>();
275         formatList.add(format);  // Place in the one format based on contact hours
276         return formatList;
277     }
278 
279 
280     private String _fixBadStartEndTerm(String startTerm, String endTerm) {
281         if (startTerm == null || endTerm == null) {
282             return endTerm;
283         }
284         String prefix = "kuali.atp.";
285         int startTermVal = _extractModifiedTerm(startTerm);
286         int endTermVal = _extractModifiedTerm(endTerm);
287         if (endTermVal < startTermVal) {
288             int year = endTermVal / 100;
289             String result =  prefix + (year + 1) + endTerm.substring(prefix.length() + 4);
290             return result;
291         }
292 
293         return endTerm;  //To change body of created methods use File | Settings | File Templates.
294     }
295 
296     // Assumes file stores info in
297     // <short name>/CLU type/LUI type
298     // e.g. LEC/kuali.lu.type.activity.LectureORSeminar/kuali.lui.type.activity.offering.LectureORSeminar
299     // Short names are LEC, DIS, LAB, EXP
300     private List<Map<String, List<String>>> _getCluLuiActivityTypes(String fileName) {
301         // cluLuiActivityTypes.txt
302 
303         int firstDataFromFile = 0;
304         int secondDataFromFile = 2;
305         List<Map<String, List<String>>> listOfMaps = new ArrayList<Map<String, List<String>>>();
306         Map<String, List<String>> shortNameToCluLuiTypes = new HashMap<String, List<String>>();
307         Map<String, List<String>> cluTypeToShortNameLuiTypeList = new HashMap<String, List<String>>();
308         List<String> lines = _loadDataFromFile(fileName);
309         for (String line: lines) {
310             List<String> types = new ArrayList<String>(Arrays.asList(line.split("/")));
311             //Code Modified for JIRA 8727
312             if(!types.isEmpty()){
313                 shortNameToCluLuiTypes.put(types.get(firstDataFromFile), types.subList(1, types.size()));
314             }
315 
316             List<String> shortNameLuiType = new ArrayList<String>();
317             //Code Modified for JIRA 8727
318             if(!types.isEmpty() && types.size()>2){
319                 shortNameLuiType.add(types.get(firstDataFromFile)); // add the short name like LEC
320                 shortNameLuiType.add(types.get(secondDataFromFile)); // add the lui type
321             }
322 
323             cluTypeToShortNameLuiTypeList.put(types.get(1), shortNameLuiType);
324         }
325         listOfMaps.add(shortNameToCluLuiTypes);
326         listOfMaps.add(cluTypeToShortNameLuiTypeList);
327         return listOfMaps;
328     }
329 
330     @Transactional
331     private void _modifyFormatsForCourse(String courseId,
332                                         Map<String, List<String>> courseCodeToFormatList,
333                                         Map<String, List<String>> shortNameToCluLuiTypes,
334                                         Map<String, List<String>> cluTypeToShortNameLuiTypeList)
335             throws InvalidParameterException, MissingParameterException, DoesNotExistException,
336             PermissionDeniedException, OperationFailedException, DataValidationErrorException, DependentObjectsExistException, UnsupportedActionException, AlreadyExistsException, CircularRelationshipException, CircularReferenceException, ReadOnlyException, VersionMismatchException {
337         // Use courseId = BSCI105_COURSE_ID
338         CourseInfo course = courseService.getCourse(courseId, context);
339 
340 
341         // Check to see if this course has been processed.  The default (pre-change) is a single Format with
342         // four ActivityInfo objects.  After the modifications,
343         FormatInfo firstFormat = course.getFormats().get(0);
344         if (firstFormat.getActivities().size() < 4) { // By default, old UMD data has exactly 4 formats
345             // This course object has already been modified
346             System.err.println("Skipping: " + course.getCode() + " (" + course.getId() + ")");
347             return;
348         }
349         // If the format isn't in courseCodeToFormatList (which is from a spreadsheet by UMD), then
350         // figure out the format based on contact hours of Activities
351         String courseCode = course.getCode();
352         List<String> formatList = courseCodeToFormatList.get(courseCode); // This is a list of short names
353         if (formatList == null) { // Can't find this from the spreadsheet data, so infer the format
354             formatList = _inferFormat(cluTypeToShortNameLuiTypeList, firstFormat);
355         }
356         // Now redo the formats
357         List<FormatInfo> newFormatList = new ArrayList<FormatInfo>();
358         int firstLuiType = 0;
359         for (String format: formatList) {
360             List<String> shortNames = Arrays.asList(format.split("/"));
361             List<String> activityTypes = new ArrayList<String>();
362             for (String shortName: shortNames) {
363                 // Get the CLU type which is at index 0 (LUI type is at index 1)
364                 // Short name is currently LEC, LAB, DIS, or EXP
365                     activityTypes.add(shortNameToCluLuiTypes.get(shortName).get(firstLuiType));
366 
367             }
368             FormatInfo info = new FormatInfo(firstFormat);
369             List<ActivityInfo> newActivityTypes = new ArrayList<ActivityInfo>();
370             for (ActivityInfo actInfo: firstFormat.getActivities()) {
371                 if (!activityTypes.contains(actInfo.getTypeKey())) {
372                     // Skip if this type is not part of the format
373                     continue;
374                 }
375                 ActivityInfo actCopy = new ActivityInfo(actInfo);
376                 actCopy.setId(null); // Make a copy
377                 AmountInfo amtInfo = actCopy.getContactHours();
378                 if (amtInfo.getUnitQuantity() == null ||
379                         "0".equals(amtInfo.getUnitQuantity().trim())) {
380                     amtInfo.setUnitQuantity("3"); // Have non-zero contact hours.  Pick 3 as a reasonable default
381                 }
382                 newActivityTypes.add(actCopy);
383             }
384             info.setActivities(newActivityTypes);
385             info.setId(null); // Need new IDs for these types
386             newFormatList.add(info);
387         }
388         course.setFormats(newFormatList);
389 
390         // Fix bad start/end terms
391         String oldStartTerm = course.getStartTerm();
392         String oldEndTerm = course.getEndTerm();
393         String newEndTerm = _fixBadStartEndTerm(oldStartTerm, oldEndTerm);
394         course.setEndTerm(newEndTerm);
395 
396         for (AttributeInfo attr: course.getAttributes()) {
397             // This is compensate for some bad data.  This sets the lastTermOffered to the endTerm
398             if (attr.getKey().equals("lastTermOffered")) {
399                 attr.setValue(course.getEndTerm());
400             }
401         }
402         // Fix campus locations if necessary
403         if (course.getCampusLocations().isEmpty()) {
404             course.getCampusLocations().add("NO");
405         }
406         // Fix finalExamStatus if need be
407         boolean foundFinalExamStatus = false;
408         for (AttributeInfo attr: course.getAttributes()) {
409             if (attr.getKey().equals("finalExamStatus")) {
410                 foundFinalExamStatus = true;
411             }
412         }
413         if (!foundFinalExamStatus) {
414             AttributeInfo newAttr = new AttributeInfo();
415             newAttr.setKey("finalExamStatus");
416             newAttr.setValue("STD");
417             course.getAttributes().add(newAttr);
418         }
419 
420         CourseInfo updated = courseService.updateCourse(course.getId(), course, context);
421         System.err.println("   Updated: " + course.getCode() + " (" + course.getId() + ")");
422     }
423 
424     private int _extractModifiedTerm(String term) {
425         String prefix = "kuali.atp.";
426         String yearSubstr = term.substring(prefix.length(), prefix.length() + 4);
427         String season = term.substring(prefix.length() + 4);
428         String suffix;
429         if (season.equals("Spring")) {
430             suffix = "02";
431         } else if (season.equals("Winter")) {
432             suffix = "01";
433         } else if (season.equals("Summer1")) {
434             suffix = "05";
435         } else if (season.equals("Summer2")) {
436             suffix = "07";
437         } else if (season.equals("Fall")) {
438             suffix = "08";
439         } else {
440             throw new RuntimeException("Illegal season name");
441         }
442         String result = yearSubstr + suffix;
443         Integer val = new Integer(result);
444         return val;
445     }
446 
447     // Each line of the file should look like
448     // (BSCI105) [LEC/LAB|LEC]
449     // Course code is in parentheses followed by formats in brackets.  Multiple formats (as in this case)
450     // are separated by the vertical bar, which is on the same key as the backslash on standard Western keyboards
451     private Map<String, List<String>> _loadCourseCodeToFormatList(String fileName) {
452         // courseCodesToFormats.txt
453         Map<String, List<String>> courseCodesToFormatList = new HashMap<String, List<String>>();
454         List<String> lines = _loadDataFromFile(fileName);
455         for (String line: lines) {
456             line = line.trim();
457             if (line.isEmpty()) {
458                 continue;
459             }
460             int leftParenIndex = line.indexOf('(');
461             int rightParenIndex = line.indexOf(')');
462             String courseCode = line.substring(leftParenIndex + 1, rightParenIndex);
463             int leftBracketIndex = line.indexOf('[');
464             int rightBracketIndex = line.indexOf(']');
465             String formatStr = line.substring(leftBracketIndex + 1, rightBracketIndex);
466             // Now split
467             List<String> formats = new ArrayList<String>(Arrays.asList(formatStr.split(Pattern.quote("|"))));
468             courseCodesToFormatList.put(courseCode, formats);
469         }
470         return courseCodesToFormatList;
471     }
472 
473     private void _getFormatOfferingInfoForOneCourse() throws InvalidParameterException, MissingParameterException, DoesNotExistException, PermissionDeniedException, OperationFailedException, IOException {
474         Course course = courseService.getCourse(BSCI105_COURSE_ID, context);
475         List<CourseOfferingInfo> coList =
476                 coService.getCourseOfferingsByCourse(BSCI105_COURSE_ID, context);
477         PrintStream pOut = _createPrintStream("formatData.txt");
478         for (CourseOfferingInfo info: coList) {
479             pOut.println(info.getCourseOfferingCode() + " ------------------------");
480             List<FormatOfferingInfo> foInfos =
481                     coService.getFormatOfferingsByCourseOffering(info.getId(), context);
482 
483             for (FormatOfferingInfo fo: foInfos) {
484                 pOut.println("Format offering : " + fo.getId());
485                 pOut.println("Format id: " + fo.getFormatId());
486                 List<String> aoTypeKeys = fo.getActivityOfferingTypeKeys();
487                 pOut.print("AO Type keys: ");
488                 for (String key: aoTypeKeys) {
489                     pOut.print(key + " ");
490                 }
491                 pOut.println();
492                 List<ActivityOfferingInfo> aoInfos =
493                         coService.getActivityOfferingsByFormatOffering(fo.getId(), context);
494                 int count = 0;
495                 for (ActivityOfferingInfo ao: aoInfos) {
496                     // Print ao
497                     pOut.println(count + "/" + ao.getId() + "/" + ao.getTypeKey());
498                 }
499             }
500         }
501     }
502     private PrintStream _createPrintStream(String fileName) throws IOException {
503         File file = new File(pathPrefix, fileName);
504         if (file.exists()) {
505             file.delete();
506         }
507         file.createNewFile();
508         FileOutputStream fos = new FileOutputStream(file);
509         PrintStream pOut = new PrintStream(fos);
510         return pOut;
511     }
512 
513     private List<String> _findInvalidCoursesAndIds(String courseIdFilename, String validCourseCodesFilename) throws IOException {
514         List<String> validCourseCodes = _loadDataFromFile(validCourseCodesFilename);
515         List<String> allCourseIds = _loadDataFromFile(courseIdFilename);
516         int count = 0;
517         List<String> badCourses = new ArrayList<String>();
518         for (String courseId: allCourseIds) {
519 
520             try {
521                 count++;
522                 if (count % 50 == 1) {
523                     System.err.println("count = " + count);
524                 }
525                 Course course = courseService.getCourse(courseId, context);
526                 if (course.getCode() == null) {
527                     System.err.println("Null course code: " + courseId);
528                     continue;
529                 }
530                 if (!validCourseCodes.contains(course.getCode())) {
531                     // This must be an invalid course
532                     String badCourse = course.getCode() + "/" + course.getId() + "/" + course.getVersion().getVersionIndId();
533                     badCourses.add(badCourse);
534                 }
535             } catch (Exception e) {
536                 System.err.println("Error with: " + courseId + " " + e.getMessage());
537             }
538         }
539         System.err.println("count = " + count);
540         return badCourses;
541     }
542 
543     private List<String> _loadValidCourses(String filePath) {
544         List<String> courseData = _loadDataFromFile(filePath);
545         for (int i = 0; i < courseData.size(); i++) {
546             courseData.set(i, courseData.get(i).substring(0, 7));
547         }
548         return courseData;
549     }
550 
551     private void _checkDuplicateCourses(String filePath) {
552         List<String> courseData = _loadDataFromFile(filePath);
553         String prev = null;
554         for (String data: courseData) {
555             String prefix = data.substring(0, 7);
556             if (prev != null) {
557                 if (prefix.equals(prev)) {
558                     System.err.println("Same: " + prefix);
559                 }
560             }
561             prev = prefix;
562         }
563         System.err.println("Done");
564     }
565     private void _sortValidCourses() throws IOException {
566         List<String> courseData = _loadDataFromFile("validCourseInfo.txt");
567         if (courseData == null) {
568             return;
569         }
570         Collections.sort(courseData);
571         // Print info
572         _writeInfoToFile(courseData, "validCourseInfo.sorted.txt");
573     }
574 
575     private List<String> _loadDataFromFile(String fileName) {
576         return _loadDataFromFile(fileName, null);
577     }
578 
579     private List<String> _loadDataFromFile(String fileName, BasicFilter filter) {
580         Scanner scanner = null;
581         String filePath = pathPrefix + fileName;
582         try {
583             scanner = new Scanner(new File(pathPrefix, fileName));
584 
585         } catch (FileNotFoundException e) {
586             LOGGER.warn("No file found at: " + filePath);
587             return null;
588         }
589         List<String> courseData = new ArrayList<String>();
590         while (scanner.hasNextLine()) {
591             String line = scanner.nextLine().trim();
592             if (!line.isEmpty() && !line.startsWith("#")) { // Lines with # refer to comments in data
593                 if (filter == null || filter.permits(line)) {
594                     courseData.add(line);
595                 }
596             }
597         }
598         return courseData;
599     }
600 
601     private void _writeInfoToFile(List<String> listOfStrings, String fileName) throws IOException {
602         PrintStream pOut = _createPrintStream(fileName);
603         for (String line: listOfStrings) {
604             pOut.println(line);
605         }
606         pOut.close();
607     }
608 
609     private void _writeValidCourses(String courseIdsFilePath) throws IOException {
610         ContextInfo context = new ContextInfo();
611         List<String> validCourseInfo =  new ArrayList<String>();
612         List<String> courseIds = _loadDataFromFile(courseIdsFilePath);
613         if (courseIds == null) {
614             return;
615         }
616         int count = 0;
617         for (String courseId: courseIds) {
618             try {
619                 List<CourseOfferingInfo> coInfos =
620                         coService.getCourseOfferingsByCourse(courseId, context);
621                 if (!coInfos.isEmpty()) {
622                     count++;
623                 } else {
624                     // Skip courses with no COs.
625                     continue;
626                 }
627                 // Map the id to course code
628                 CourseInfo course = courseService.getCourse(courseId, context);
629                 String courseCode = "None";
630                 if (course.getCode() != null) {
631                     courseCode = course.getCode();
632                 }
633                 validCourseInfo.add(course.getCode() + "/" + course.getId() + "/" + course.getVersion().getVersionIndId());
634             } catch (Exception ex) {
635                 LOGGER.info("Exception thrown on: " + courseId);
636             }
637             if (count % 50 == 1) {
638                 LOGGER.warn("count = " + count);
639             }
640         }
641         // Print info
642         _writeInfoToFile(validCourseInfo, "validCourseInfo.txt");
643     }
644 
645     private Map<String, List<String>> _determineFormats(SpreadsheetData spreadsheet) throws IOException {
646         Map<String, List<String>> courseCodeToFormats = new TreeMap<String, List<String>>();
647         Set<String> uniqueFormats = new HashSet<String>();
648         for (SpreadsheetRowData rowData: spreadsheet) {
649             String courseCode = rowData.getValue("CM Course");
650             List<String> formats = courseCodeToFormats.get(courseCode);
651             if (formats == null) {
652                 // If nothing is in the map, create and map it
653                 formats = new ArrayList<String>();
654                 courseCodeToFormats.put(courseCode, formats);
655             }
656             String formatName = rowData.getValue("shortName");
657             if (!formats.contains(formatName)) {
658                 formats.add(formatName);
659             }
660             uniqueFormats.add(formatName);
661         }
662         // Print it!
663         _printFormats(courseCodeToFormats, uniqueFormats, "courseCodeToFormats.txt");
664         return courseCodeToFormats;
665     }
666 
667     private void _printFormats(Map<String, List<String>> courseCodeToFormats, Set<String> uniqueFormats, String fileName) throws IOException {
668         // Called by _determineFormats which is the entry point
669         PrintStream pOut = _createPrintStream(fileName);
670         //Code Changed for JIRA-8997 - SONAR Critical issues - Performance - Inefficient use of keySet iterator instead of entrySet iterator
671         for(Map.Entry<String, List<String>> entry: courseCodeToFormats.entrySet()){
672             String courseCode = entry.getKey();
673             pOut.print("(" + courseCode + ") ");
674             List<String> formats = entry.getValue();
675             boolean firstTime = true;
676             for (String format: formats) {
677                 if (firstTime) {
678                     firstTime = false;
679                     pOut.print("[");
680                 } else {
681                     pOut.print("|"); // separator is |
682                 }
683                 pOut.print(format);
684             }
685             pOut.println("]");
686         }
687         boolean firstTime = true;
688         for (String format: uniqueFormats) {
689             if (firstTime) {
690                 firstTime = false;
691                 pOut.print("[");
692             } else {
693                 pOut.print(", ");
694             }
695             pOut.print("(" + format + ")");
696         }
697         pOut.println("]");
698         pOut.close();
699     }
700 
701     private void _writeCourseIdsAndCodes(String courseIdsFilePath) throws IOException {
702         ContextInfo context = new ContextInfo();
703         Map<String, String> courseCodeToCourseId = new TreeMap<String, String>();
704         List<String> courseIds = _loadDataFromFile(courseIdsFilePath);
705         if (courseIds == null) {
706             return;
707         }
708         int count = 0;
709         for (String courseId: courseIds) {
710             try {
711                 List<CourseOfferingInfo> coInfos =
712                         coService.getCourseOfferingsByCourse(courseId, context);
713                 if (!coInfos.isEmpty()) {
714                     count++;
715                 }
716                 // Map the id to course code
717                 CourseInfo course = courseService.getCourse(courseId, context);
718                 String courseCode = "None";
719                 if (course.getCode() != null) {
720                     courseCode = course.getCode();
721                 }
722                 if (courseCodeToCourseId.containsKey(courseCode)) {
723                     courseCode = _findNewCourseCode(courseCode, courseCodeToCourseId);
724                 }
725                 courseCodeToCourseId.put(courseCode, courseId);
726             } catch (Exception ex) {
727                 LOGGER.info("Exception thrown on: " + courseId);
728             }
729             if (count % 100 == 1) {
730                 LOGGER.warn("count = " + count);
731             }
732         }
733         // Print info
734         PrintStream pOut = _createPrintStream("courseIdsAndCodes.txt");
735         //Code Changed for JIRA-8997 - SONAR Critical issues - Performance - Inefficient use of keySet iterator instead of entrySet iterator
736         for(Map.Entry<String, String> entry: courseCodeToCourseId.entrySet()){
737             pOut.println(entry.getKey() + "/" + entry.getValue());
738         }
739         pOut.close();
740         LOGGER.info("Total courses: " + courseIds.size());
741         LOGGER.info("Courses with COs: " + count);
742     }
743 
744     private String _findNewCourseCode(String codePrefix, Map<String, String> courseCodeToCourseId) {
745         for (int i = 0; i < 1000; i++) {
746             String testCode = codePrefix + i;
747             if (!courseCodeToCourseId.containsKey(testCode)) {
748                 return testCode;
749             }
750         }
751         return null;
752     }
753 
754     private List<String> _loadCourseIdsDefault() {
755         return _loadDataFromFile("courseIds.txt");
756     }
757 
758     public ContextInfo getContext() {
759         return context;
760     }
761 
762 
763 }