001package edu.pdx.cs410J.grader; 002 003import com.google.common.annotations.VisibleForTesting; 004import edu.pdx.cs410J.ParserException; 005import edu.pdx.cs410J.grader.gradebook.*; 006import org.slf4j.Logger; 007import org.slf4j.LoggerFactory; 008 009import java.io.*; 010import java.util.ArrayList; 011import java.util.List; 012import java.util.Optional; 013import java.util.regex.Matcher; 014import java.util.regex.Pattern; 015 016public class ProjectGradesImporter { 017 static final Pattern scorePattern = Pattern.compile("(\\d+\\.?\\d*) out of (\\d+\\.?\\d*)", Pattern.CASE_INSENSITIVE); 018 019 private final GradeBook gradeBook; 020 private final Assignment assignment; 021 private final Logger logger; 022 023 public ProjectGradesImporter(GradeBook gradeBook, Assignment assignment, Logger logger) { 024 this.gradeBook = gradeBook; 025 this.assignment = assignment; 026 this.logger = logger; 027 } 028 029 public static ProjectScore getScoreFrom(Reader reader) throws ScoreNotFoundException { 030 BufferedReader br = new BufferedReader(reader); 031 Optional<String> scoreLine = br.lines().filter(scorePattern.asPredicate()).findFirst(); 032 033 if (scoreLine.isPresent()) { 034 Matcher matcher = scorePattern.matcher(scoreLine.get()); 035 if (matcher.find()) { 036 return new ProjectScore(matcher.group(1), matcher.group(2)); 037 038 } else { 039 throw new IllegalStateException("Matcher didn't match \"" + scoreLine.get() + "\""); 040 } 041 042 } else { 043 throw new ScoreNotFoundException(); 044 } 045 046 } 047 048 public void recordScoreFromProjectReport(String studentId, Reader report) throws ScoreNotFoundException { 049 ProjectScore score = getScoreFrom(report); 050 051 if (score.getTotalPoints() != this.assignment.getPoints()) { 052 String message = "Assignment " + this.assignment.getName() + " should be worth " + this.assignment.getPoints() + 053 " points, but the report for " + studentId + " was out of " + score.getTotalPoints(); 054 throw new IllegalStateException(message); 055 } 056 057 Optional<Student> maybeStudent = gradeBook.getStudent(studentId); 058 if (maybeStudent.isEmpty()) { 059 warn("Student \"" + studentId + "\" not found in gradebook"); 060 return; 061 } 062 063 Student student = maybeStudent.get(); 064 Grade grade = student.getGrade(this.assignment); 065 if (grade == null) { 066 grade = new Grade(assignment, score.getScore()); 067 student.setGrade(assignment.getName(), grade); 068 069 } else { 070 grade.setScore(score.getScore()); 071 } 072 073 info("Recorded grade of " + score.getScore() + " for " + studentId); 074 } 075 076 private void info(String message) { 077 this.logger.info(message); 078 } 079 080 private void warn(String message) { 081 logger.warn(message); 082 } 083 084 static class ProjectScore { 085 private final double score; 086 private final double totalPoints; 087 088 private ProjectScore(String score, String totalPoints) { 089 this.score = Double.parseDouble(score); 090 this.totalPoints = Double.parseDouble(totalPoints); 091 } 092 093 public double getScore() { 094 return this.score; 095 } 096 097 public double getTotalPoints() { 098 return this.totalPoints; 099 } 100 } 101 102 public static void main(String[] args) { 103 String gradeBookFileName = null; 104 String assignmentName = null; 105 List<String> projectFileNames = new ArrayList<>(); 106 107 for (String arg : args) { 108 if (gradeBookFileName == null) { 109 gradeBookFileName = arg; 110 111 } else if (assignmentName == null) { 112 assignmentName = arg; 113 114 } else { 115 projectFileNames.add(arg); 116 } 117 } 118 119 usageIfNull(gradeBookFileName, "Missing grade book file"); 120 usageIfNull(assignmentName, "Missing assignment name"); 121 usageIfEmpty(projectFileNames, "No project file names provided"); 122 123 GradeBook gradeBook = getGradeBook(gradeBookFileName); 124 Assignment assignment = getAssignment(assignmentName, gradeBook); 125 Logger logger = LoggerFactory.getLogger(ProjectGradesImporter.class.getPackage().getName()); 126 127 ProjectGradesImporter importer = new ProjectGradesImporter(gradeBook, assignment, logger); 128 129 for (String projectFileName : projectFileNames) { 130 File projectFile = getProjectFile(projectFileName); 131 String studentId = getStudentIdFromFileName(projectFile); 132 try { 133 importer.recordScoreFromProjectReport(studentId, new FileReader(projectFile)); 134 135 } catch (IllegalStateException ex) { 136 throw new IllegalStateException("While recording score from " + projectFileName, ex); 137 138 } catch (FileNotFoundException ex) { 139 throw new IllegalStateException("Could not find file \"" + projectFile + "\""); 140 } catch (ScoreNotFoundException e) { 141 logger.warn("Could not find score in " + projectFileName); 142 } 143 } 144 145 saveGradeBookIfModified(gradeBook, gradeBookFileName); 146 } 147 148 private static void saveGradeBookIfModified(GradeBook gradeBook, String gradeBookFileName) { 149 if (gradeBook.isDirty()) { 150 File file = new File(gradeBookFileName); 151 try { 152 XmlDumper dumper = new XmlDumper(file); 153 dumper.dump(gradeBook); 154 155 } catch (IOException e) { 156 usage("Can't write grade book in \"" + gradeBookFileName + "\""); 157 } 158 } 159 } 160 161 private static String getStudentIdFromFileName(File projectFile) { 162 String fileName = projectFile.getName(); 163 int index = fileName.lastIndexOf('.'); 164 if (index < 0) { 165 return usage("Project file \"" + fileName + "\" does not have a file extension"); 166 } 167 168 return fileName.substring(0, index); 169 } 170 171 private static File getProjectFile(String projectFileName) { 172 File projectFile = new File(projectFileName); 173 if (!projectFile.exists()) { 174 return usage("Project file \"" + projectFileName + "\" does not exist"); 175 176 } else if (!projectFile.isFile()) { 177 return usage("Project file \"" + projectFileName + "\" is not a file"); 178 } 179 180 return projectFile; 181 } 182 183 private static Assignment getAssignment(String assignmentName, GradeBook gradeBook) { 184 Assignment assignment = gradeBook.getAssignment(assignmentName); 185 if (assignment == null) { 186 return usage("Could not find assignment \"" + assignmentName + "\" in grade book"); 187 } 188 return assignment; 189 } 190 191 private static GradeBook getGradeBook(String gradeBookFileName) { 192 try { 193 XmlGradeBookParser parser = new XmlGradeBookParser(gradeBookFileName); 194 return parser.parse(); 195 196 } catch (IOException ex) { 197 return usage("Couldn't read grade book file \"" + gradeBookFileName + "\""); 198 199 } catch (ParserException ex) { 200 return usage("Couldn't parse grade book file \"" + gradeBookFileName + "\""); 201 } 202 } 203 204 private static void usageIfEmpty(List<String> list, String message) { 205 if (list.isEmpty()) { 206 usage(message); 207 } 208 } 209 210 private static void usageIfNull(String argument, String message) { 211 if (argument == null) { 212 usage(message); 213 } 214 } 215 216 private static <T> T usage(String message) { 217 PrintStream err = System.err; 218 219 err.println("** " + message); 220 221 err.println("java ProjectGradesImporter gradeBookFileName assignmentName projectFileName+"); 222 err.println(); 223 err.println("Imports grades (of the form \"5.8 out of 6.0\") from project reports into a grade book."); 224 err.println("The name of the project report file begins with the student's id."); 225 err.println(); 226 err.println(" gradeBookFileName Name of file containing grade book"); 227 err.println(" assignmentName Assignment/project whose score is to be recorded"); 228 err.println(" projectFileName Name of file containing graded project"); 229 err.println(); 230 231 System.exit(1); 232 233 return null; 234 } 235 236 @VisibleForTesting 237 static class ScoreNotFoundException extends Exception { 238 239 } 240}