001package edu.pdx.cs410J.grader.gradebook;
002
003import edu.pdx.cs410J.ParserException;
004
005import java.io.File;
006import java.io.FileNotFoundException;
007import java.io.IOException;
008import java.io.PrintWriter;
009import java.time.LocalDateTime;
010import java.util.ArrayList;
011import java.util.List;
012import java.util.stream.Collectors;
013
014/**
015 * This class represent the grade a student got on an assignment.
016 */
017public class Grade extends NotableImpl {
018  /**
019   * The assignment is not complete
020   */
021  public static final double INCOMPLETE = -1.0;
022
023  /**
024   * Some work has been submitted, but no grade has been assigned
025   */
026  public static final double NO_GRADE = -2.0;
027
028  private final String assignmentName;
029  private double score;         // Score student received
030  private final List<SubmissionInfo> submissionInfo = new ArrayList<>();
031
032  /**
033   * Creates a <code>Grade</code> for a given assignment
034   */
035  public Grade(String assignmentName, double score) {
036    this.assignmentName = assignmentName;
037    this.score = score;
038    this.setDirty(true);  // Initially dirty
039  }
040
041  public Grade(Assignment project, double score) {
042    this(project.getName(), score);
043  }
044
045  /**
046   * Returns the name of the assignment that this <code>Grade</code>
047   * is for.
048   */
049  public String getAssignmentName() {
050    return this.assignmentName;
051  }
052
053  /**
054   * Returns the score the student received on the assignment.
055   */
056  public double getScore() {
057    return this.score;
058  }
059
060  public boolean isIncomplete() {
061    return this.getScore() == INCOMPLETE;
062  }
063
064  /**
065   * Sets the score the student received on the assignment.
066   */
067  public void setScore(double score) {
068    this.score = score;
069    this.setDirty(true);
070  }
071
072  private static PrintWriter err = new PrintWriter(System.err, true);
073
074  /**
075   * Returns a brief textual description of this <code>Grade</code>
076   */
077  public String toString() {
078    return this.assignmentName + ": " + this.score;
079  }
080
081  /**
082   * Prints usage information about the main program.
083   */
084  private static void usage() {
085    err.println("\njava Grade [options] -xmlFile xmlFile " +
086                "-id id -assignment name");
087    err.println("  where [options] ares:");
088    err.println("  -score points          How many points student got");
089    err.println("  -incomplete            Notes that the assignment "
090                + "was INCOMPLETE");
091    err.println("  -no-grade              No grade was given for the "
092                + "assignment");
093    err.println("  -note note             A note about the grade");
094    err.println("\n");
095    System.exit(1);
096  }
097
098  /**
099   * A main program that creates/edits a student's grade.
100   */
101  public static void main(String[] args) {
102    String xmlFile = null;
103    String id = null;
104    String assignment = null;
105    String score = null;
106    boolean incomplete = false;
107    boolean noGrade = false;
108    String note = null;
109
110    // Parse the command line
111    for (int i = 0; i < args.length; i++) {
112      if (args[i].equals("-xmlFile")) {
113        if (++i >= args.length) {
114          err.println("** Missing XML file name");
115          usage();
116        }
117
118        xmlFile = args[i];
119
120      } else if (args[i].equals("-id")) {
121        if (++i >= args.length) {
122          err.println("** Missing student id");
123          usage();
124        }
125
126        id = args[i];
127
128      } else if (args[i].equals("-assignment")) {
129        if (++i >= args.length) {
130          err.println("** Missing assignment name");
131          usage();
132        }
133
134        assignment = args[i];
135
136      } else if (args[i].equals("-score")) {
137        if (++i >= args.length) {
138          err.println("** Missing score");
139          usage();
140        }
141
142        score = args[i];
143
144      } else if (args[i].equals("-incomplete")) {
145        incomplete = true;
146
147      } else if (args[i].equals("-no-grade")) {
148        noGrade = true;
149
150      } else if (args[i].equals("-note")) {
151        if (++i >= args.length) {
152          err.println("** Missing note text");
153          usage();
154        }
155
156        note = args[i];
157
158      } else if (args[i].startsWith("-")) {
159        err.println("** Unknown command line option: " + args[i]);
160        usage();
161
162      } else {
163        err.println("** Spurious command line: " + args[i]);
164        usage();
165      }
166    }
167
168    // Verify command line
169    if (xmlFile == null) {
170      err.println("** No XML file specified");
171      usage();
172    }
173
174    if (id == null) {
175      err.println("** No student id specified");
176      usage();
177      return;
178    }
179
180    if (assignment == null) {
181      err.println("** No assignment name specified");
182      usage();
183    }
184
185    File file = new File(xmlFile);
186    if (!file.exists()) {
187      err.println("** Grade book file " + xmlFile + 
188                  " does not exist");
189      System.exit(1);
190    }
191
192    // Parse XML file
193    GradeBook book = null;
194    try {
195      XmlGradeBookParser parser = new XmlGradeBookParser(file);
196      book = parser.parse();
197
198    } catch (FileNotFoundException ex) {
199      err.println("** Could not find file: " + ex.getMessage());
200      System.exit(1);
201
202    } catch (IOException ex) {
203      err.println("** IOException during parsing: " + ex.getMessage());
204      System.exit(1);
205
206    } catch (ParserException ex) {
207      err.println("** Exception while parsing " + file + ": " + ex);
208      System.exit(1);
209    }
210
211    if (!book.containsStudent(id)) {
212      err.println("** No student with id: " + id);
213      System.exit(1);
214    }
215
216    // Get the student
217    Student student = book.getStudent(id).get();
218
219    // Get the grade
220    Grade grade = student.getGrade(assignment);
221    if (grade == null) {
222      // Create a new Grade
223      double s;
224
225      if (incomplete) {
226        s = Grade.INCOMPLETE;
227
228      } else if (noGrade) {
229        s = Grade.NO_GRADE;
230
231      } else if (score == null) {
232        err.println("** No score for " + assignment);
233        System.exit(1);
234        s = -4.2;
235
236      } else {
237        try {
238          s = Double.parseDouble(score);
239
240        } catch (NumberFormatException ex) {
241          err.println("** Score is not a double: " + score);
242          System.exit(1);
243          s = -4.2;
244        }
245      }
246
247      grade = new Grade(assignment, s);
248      student.setGrade(assignment, grade);
249
250    } else {
251      // Set the grade
252      if (incomplete) {
253        grade.setScore(Grade.INCOMPLETE);
254
255      } else if (noGrade) {
256        grade.setScore(Grade.NO_GRADE);
257
258      } else if (score != null) {
259        try {
260          grade.setScore(Double.parseDouble(score));
261
262        } catch (NumberFormatException ex) {
263          err.println("** Score is not a double: " + score);
264        }
265      }
266    }
267
268    if (note != null) {
269      grade.addNote(note);
270    }
271
272    // Write the changes back out to the XML file
273    try {
274      XmlDumper dumper = new XmlDumper(file);
275      dumper.dump(book);
276
277    } catch (IOException ex) {
278      err.println("** While dumping to " + file + ": " + ex);
279      System.exit(1);
280    }
281  }
282
283  public boolean isNotGraded() {
284    return this.getScore() == Grade.NO_GRADE;
285  }
286
287  public List<LocalDateTime> getSubmissionTimes() {
288    return submissionInfo.stream().map(SubmissionInfo::getSubmissionTime).collect(Collectors.toList());
289  }
290
291  public SubmissionInfo noteSubmission(LocalDateTime submissionTime) {
292    SubmissionInfo info = new SubmissionInfo();
293    info.setSubmissionTime(submissionTime);
294    return noteSubmission(info);
295  }
296
297  public SubmissionInfo noteSubmission(SubmissionInfo info) {
298    this.setDirty(true);
299    this.submissionInfo.add(info);
300
301    return info;
302  }
303
304  public List<SubmissionInfo> getSubmissionInfos() {
305    return this.submissionInfo;
306  }
307
308  public static class SubmissionInfo {
309    private LocalDateTime submissionTime;
310    private Double estimatedHours;
311    private boolean isLate;
312
313    public LocalDateTime getSubmissionTime() {
314      return submissionTime;
315    }
316
317    public SubmissionInfo setSubmissionTime(LocalDateTime submissionTime) {
318      this.submissionTime = submissionTime;
319      return this;
320    }
321
322    public Double getEstimatedHours() {
323      return estimatedHours;
324    }
325
326    public SubmissionInfo setEstimatedHours(Double estimatedHours) {
327      this.estimatedHours = estimatedHours;
328      return this;
329    }
330
331    public SubmissionInfo setIsLate(boolean isLate) {
332      this.isLate = isLate;
333      return this;
334    }
335
336    public boolean isLate() {
337      return isLate;
338    }
339  }
340}