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}