001package edu.pdx.cs410J.grader.gradebook; 002 003import edu.pdx.cs410J.grader.gradebook.Assignment.ProjectType; 004import org.w3c.dom.*; 005 006import javax.xml.parsers.DocumentBuilder; 007import javax.xml.parsers.DocumentBuilderFactory; 008import javax.xml.parsers.ParserConfigurationException; 009import javax.xml.transform.TransformerException; 010import java.io.File; 011import java.io.FileWriter; 012import java.io.IOException; 013import java.io.PrintWriter; 014import java.time.LocalDateTime; 015import java.util.Iterator; 016import java.util.List; 017import java.util.Objects; 018 019import static edu.pdx.cs410J.grader.gradebook.GradeBook.LetterGradeRanges.LetterGradeRange; 020 021/** 022 * This class dumps the contents of a <code>GradeBook</code> to an XML 023 * file. By default, the students in the grade book are dumped to XML 024 * files in the same directory as the grade book's XML file. 025 * 026 * @author David Whitlock 027 * @since Fall 2000 028 */ 029public class XmlDumper extends XmlHelper { 030 031 private File studentDir = null; // Where to dump student XML files 032 PrintWriter pw; // Where to dump grade book 033 034 /** 035 * Creates a new <code>XmlDumper</code> that dumps the contents of a 036 * grade book to a given file. 037 */ 038 public XmlDumper(File xmlFile) throws IOException { 039 this(new PrintWriter(new FileWriter(xmlFile), true)); 040 041 if (!xmlFile.exists()) { 042 if (!xmlFile.createNewFile()) { 043 throw new IOException("Could not create file " + xmlFile); 044 } 045 } 046 047 this.setStudentDir(xmlFile.getCanonicalFile().getParentFile()); 048 } 049 050 /** 051 * Creates a new <code>XmlDumper</code> that dumps the contents of a 052 * grade book to a file of the given name. 053 */ 054 public XmlDumper(String xmlFileName) throws IOException { 055 this(new File(xmlFileName)); 056 } 057 058 /** 059 * Creates an <code>XmlDumper</code> that dumps the contents of a 060 * grade book to a <code>PrintWriter</code>. The location of the 061 * student files is unspecified. 062 */ 063 private XmlDumper(PrintWriter pw) { 064 this.pw = pw; 065 } 066 067 /** 068 * Sets the directory in which the XML files for students are 069 * generated. 070 */ 071 public void setStudentDir(File dir) { 072 if (dir.exists() && !dir.isDirectory()) { 073 throw new IllegalArgumentException(dir + " is not a directory"); 074 } 075 076 this.studentDir = dir; 077 } 078 079 /** 080 * Dumps the contents of a <code>GradeBook</code> in XML format. 081 */ 082 public void dump(GradeBook book) throws IOException { 083 Document doc = dumpGradeBook(book, this); 084 085 try { 086 writeXmlToPrintWriter(doc, this.pw); 087 088 } catch (TransformerException ex) { 089 ex.printStackTrace(System.err); 090 System.exit(1); 091 } 092 093 dumpDirtyStudents(book); 094 095 // Mark the grade book as being clean 096 book.makeClean(); 097 } 098 099 static Document dumpGradeBook(GradeBook book, XmlHelper helper) { 100 Document doc = createDocumentForGradeBook(helper); 101 102 Element root = doc.getDocumentElement(); 103 104 appendXmlForClassName(book, root); 105 appendXmlForAssignments(book, doc, root); 106 appendXmlForLetterGradeRanges(book, doc, root); 107 appendXmlForSectionNames(book, doc, root); 108 appendXmlForStudents(book, doc, root); 109 110 return doc; 111 } 112 113 private static void appendXmlForSectionNames(GradeBook book, Document doc, Element root) { 114 for (Student.Section section : book.getSections()) { 115 String sectionName = book.getSectionName(section); 116 if (sectionName != null) { 117 root.appendChild(appendXmlForSectionName(root, section, sectionName)); 118 } 119 } 120 } 121 122 private static Element appendXmlForSectionName(Element parent, Student.Section section, String sectionName) { 123 Element sectionNameNode = appendTextElementIfValueIsNotNull(parent, "section-name", sectionName); 124 sectionNameNode.setAttribute("for-section", getSectionXmlAttributeValue(section)); 125 return sectionNameNode; 126 } 127 128 private static void appendXmlForLetterGradeRanges(GradeBook book, Document doc, Element root) { 129 for (Student.Section section : book.getSections()) { 130 Element lgrNode = appendXmlForLetterGradeRange(book, section, doc); 131 root.appendChild(lgrNode); 132 } 133 } 134 135 private static Element appendXmlForLetterGradeRange(GradeBook book, Student.Section section, Document doc) { 136 Element lgrNode = doc.createElement("letter-grade-ranges"); 137 lgrNode.setAttribute("for-section", getSectionXmlAttributeValue(section)); 138 for (LetterGradeRange range : book.getLetterGradeRanges(section)) { 139 appendXmlForLetterGradeRange(range, doc, lgrNode); 140 } 141 return lgrNode; 142 } 143 144 private static String getSectionXmlAttributeValue(Student.Section section) { 145 return Objects.toString(section, null); 146 } 147 148 private static void appendXmlForLetterGradeRange(LetterGradeRange range, Document doc, Element parent) { 149 Element node = doc.createElement("letter-grade-range"); 150 node.setAttribute("letter-grade", range.letterGrade().asString()); 151 node.setAttribute("minimum-score", String.valueOf(range.minimum())); 152 node.setAttribute("maximum-score", String.valueOf(range.maximum())); 153 154 parent.appendChild(node); 155 } 156 157 private static Document createDocumentForGradeBook(XmlHelper helper) { 158 Document doc = null; 159 160 try { 161 DocumentBuilderFactory factory = 162 DocumentBuilderFactory.newInstance(); 163 factory.setValidating(true); 164 165 DocumentBuilder builder = factory.newDocumentBuilder(); 166 builder.setErrorHandler(helper); 167 builder.setEntityResolver(helper); 168 169 DOMImplementation dom = 170 builder.getDOMImplementation(); 171 DocumentType docType = 172 dom.createDocumentType("gradebook", publicID, systemID); 173 doc = dom.createDocument(null, "gradebook", docType); 174 175 } catch (ParserConfigurationException | DOMException ex) { 176 ex.printStackTrace(System.err); 177 System.exit(1); 178 179 } 180 return doc; 181 } 182 183 private static void appendXmlForStudents(GradeBook book, Document doc, Element root) { 184 // Students 185 Element studentsNode = doc.createElement("students"); 186 for (String id : book.getStudentIds()) { 187 appendTextElementIfValueIsNotNull(studentsNode, "id", id); 188 } 189 190 root.appendChild(studentsNode); 191 } 192 193 private static void appendXmlForClassName(GradeBook book, Element root) { 194 appendTextElementIfValueIsNotNull(root, "name", book.getClassName()); 195 } 196 197 private static void appendXmlForAssignments(GradeBook book, Document doc, Element root) { 198 // assignment nodes 199 Element assignments = doc.createElement("assignments"); 200 for (String assignmentName : book.getAssignmentNames()) { 201 Assignment assign = book.getAssignment(assignmentName); 202 Element assignNode = doc.createElement("assignment"); 203 204 setAssignmentTypeAttribute(assign, assignNode); 205 appendTextElementIfValueIsNotNull(assignNode, "name", assign.getName()); 206 appendTextElementIfValueIsNotNull(assignNode, "description", assign.getDescription()); 207 appendTextElementIfValueIsNotNull(assignNode, "points", String.valueOf(assign.getPoints())); 208 appendTextElementIfValueIsNotNull(assignNode, "canvas-id", String.valueOf(assign.getCanvasId())); 209 appendTextElementIfValueIsNotNull(assignNode, "due-date", assign.getDueDate()); 210 211 doNotes(doc, assignNode, assign.getNotes()); 212 213 assignments.appendChild(assignNode); 214 215 } 216 root.appendChild(assignments); 217 } 218 219 private static void appendTextElementIfValueIsNotNull(Element parent, String elementName, LocalDateTime dateTime) { 220 if (dateTime != null) { 221 appendTextElementIfValueIsNotNull(parent, elementName, dateTime.format(DATE_TIME_FORMAT)); 222 } 223 } 224 225 private static void setAssignmentTypeAttribute(Assignment assignment, Element assignmentNode) { 226 Assignment.AssignmentType type = assignment.getType(); 227 switch (type) { 228 case PROJECT: 229 assignmentNode.setAttribute("type", "PROJECT"); 230 setProjectTypeAttribute(assignment, assignmentNode); 231 break; 232 233 case QUIZ: 234 assignmentNode.setAttribute("type", "QUIZ"); 235 break; 236 237 case OTHER: 238 assignmentNode.setAttribute("type", "OTHER"); 239 break; 240 241 case OPTIONAL: 242 assignmentNode.setAttribute("type", "OPTIONAL"); 243 break; 244 245 case POA: 246 assignmentNode.setAttribute("type", "POA"); 247 break; 248 249 default: 250 throw new IllegalArgumentException("Can't handle assignment " + 251 "type " + type); 252 } 253 } 254 255 private static void setProjectTypeAttribute(Assignment assignment, Element assignmentNode) { 256 ProjectType projectType = assignment.getProjectType(); 257 if (projectType != null) { 258 switch (projectType) { 259 case APP_CLASSES: 260 assignmentNode.setAttribute("project-type", "APP_CLASSES"); 261 break; 262 263 case TEXT_FILE: 264 assignmentNode.setAttribute("project-type", "TEXT_FILE"); 265 break; 266 267 case PRETTY_PRINT: 268 assignmentNode.setAttribute("project-type", "PRETTY_PRINT"); 269 break; 270 271 case KOANS: 272 assignmentNode.setAttribute("project-type", "KOANS"); 273 break; 274 275 case XML: 276 assignmentNode.setAttribute("project-type", "XML"); 277 break; 278 279 case REST: 280 assignmentNode.setAttribute("project-type", "REST"); 281 break; 282 283 case ANDROID: 284 assignmentNode.setAttribute("project-type", "ANDROID"); 285 break; 286 287 default: 288 throw new IllegalStateException("Can't handle project type: " + projectType); 289 } 290 } 291 292 } 293 294 private void dumpDirtyStudents(GradeBook book) { 295 book.forEachStudent(student -> { 296 if (student.isDirty()) { 297 dumpStudent(student); 298 } 299 }); 300 } 301 302 /** 303 * Creates a <code>notes</code> XML element for a given 304 * <code>List</code> of notes. 305 */ 306 private static void doNotes(Document doc, Element parent, List<String> notes) { 307 Element notesNode = doc.createElement("notes"); 308 for (String note : notes) { 309 appendTextElementIfValueIsNotNull(notesNode, "note", note); 310 } 311 312 parent.appendChild(notesNode); 313 } 314 315 /** 316 * Dumps a <code>Student</code> out to an XML file whose name is 317 * based on the student's id and resides in the 318 * <code>studentDir</code>. 319 */ 320 private void dumpStudent(Student student) { 321 Document doc = toXml(student); 322 323 // Now dump DOM tree to the file 324 File studentFile = new File(studentDir, student.getId() + ".xml"); 325 PrintWriter pw = new PrintWriter(newFileWriter(studentFile), true); 326 327 try { 328 writeXmlToPrintWriter(doc, pw); 329 330 } catch (TransformerException ex) { 331 ex.printStackTrace(System.err); 332 System.exit(1); 333 } 334 } 335 336 private FileWriter newFileWriter(File studentFile) { 337 try { 338 return new FileWriter(studentFile); 339 340 } catch (IOException ex) { 341 throw new IllegalStateException("Couldn't create FileWriter for " + studentFile, ex); 342 } 343 } 344 345 /** 346 * Returns a DOM tree that represents a <code>Student</code> 347 */ 348 public static Document toXml(Student student) { 349 Document doc = createXmlDocument(); 350 351 Element root = doc.getDocumentElement(); 352 353 appendStudentInformation(student, root); 354 appendGradesInformation(student, root); 355 appendLateInformation(student, root); 356 appendResubmittedInformation(student, root); 357 appendNotes(student, root); 358 359 return doc; 360 } 361 362 private static void appendNotes(Student student, Element parent) { 363 List<String> notes = student.getNotes(); 364 if (!notes.isEmpty()) { 365 doNotes(parent.getOwnerDocument(), parent, notes); 366 } 367 } 368 369 private static void appendResubmittedInformation(Student student, Element parent) { 370 Document doc = parent.getOwnerDocument(); 371 List<String> resubmitted = student.getResubmitted(); 372 if (!resubmitted.isEmpty()) { 373 Element resubNode = doc.createElement("resubmitted"); 374 375 for (String assignmentName : resubmitted) { 376 appendTextElementIfValueIsNotNull(resubNode, "name", assignmentName); 377 } 378 379 parent.appendChild(resubNode); 380 } 381 } 382 383 private static void appendLateInformation(Student student, Element parent) { 384 Document doc = parent.getOwnerDocument(); 385 List<String> late = student.getLate(); 386 if (!late.isEmpty()) { 387 Element lateNode = doc.createElement("late"); 388 389 for (String assignmentName : late) { 390 appendTextElementIfValueIsNotNull(lateNode, "name", assignmentName); 391 } 392 393 parent.appendChild(lateNode); 394 } 395 } 396 397 private static void appendGradesInformation(Student student, Element parent) { 398 Document doc = parent.getOwnerDocument(); 399 Iterator<String> gradeNames = student.getGradeNames().iterator(); 400 if (gradeNames.hasNext()) { 401 Element gradesNode = doc.createElement("grades"); 402 while (gradeNames.hasNext()) { 403 String gradeName = gradeNames.next(); 404 Grade grade = student.getGrade(gradeName); 405 406 Element gradeNode = doc.createElement("grade"); 407 408 appendTextElementIfValueIsNotNull(gradeNode, "name", grade.getAssignmentName()); 409 appendTextElementIfValueIsNotNull(gradeNode, "score", String.valueOf(grade.getScore())); 410 411 appendSubmissionsInformation(grade.getSubmissionInfos(), gradeNode); 412 413 doNotes(doc, gradeNode, grade.getNotes()); 414 415 if (grade.getScore() == Grade.INCOMPLETE) { 416 gradeNode.setAttribute("type", "INCOMPLETE"); 417 418 } else if (grade.getScore() == Grade.NO_GRADE) { 419 gradeNode.setAttribute("type", "NO_GRADE"); 420 } 421 422 gradesNode.appendChild(gradeNode); 423 } 424 425 parent.appendChild(gradesNode); 426 } 427 } 428 429 private static void appendSubmissionsInformation(List<Grade.SubmissionInfo> submissionInfos, Element parent) { 430 if (!submissionInfos.isEmpty()) { 431 Document doc = parent.getOwnerDocument(); 432 Element submissions = doc.createElement("submissions"); 433 parent.appendChild(submissions); 434 435 submissionInfos.forEach(info -> { 436 Element submissionInfo = doc.createElement("submission-info"); 437 if (info.isLate()) { 438 submissionInfo.setAttribute("late", "true"); 439 } 440 submissions.appendChild(submissionInfo); 441 appendSubmissionInformation(info, submissionInfo); 442 }); 443 } 444 445 } 446 447 private static void appendSubmissionInformation(Grade.SubmissionInfo info, Element parent) { 448 appendTextElementIfValueIsNotNull(parent, "date", info.getSubmissionTime()); 449 appendTextElementIfValueIsNotNull(parent, "estimated-hours", info.getEstimatedHours()); 450 } 451 452 private static void appendStudentInformation(Student student, Element root) { 453 appendTextElementIfValueIsNotNull(root, "id", student.getId()); 454 appendTextElementIfValueIsNotNull(root, "firstName", student.getFirstName()); 455 appendTextElementIfValueIsNotNull(root, "lastName", student.getLastName()); 456 appendTextElementIfValueIsNotNull(root, "nickName", student.getNickName()); 457 appendTextElementIfValueIsNotNull(root, "email", student.getEmail()); 458 appendTextElementIfValueIsNotNull(root, "major", student.getMajor()); 459 appendTextElementIfValueIsNotNull(root, "canvas-id", student.getCanvasId()); 460 appendTextElementIfValueIsNotNull(root, "letter-grade", Objects.toString(student.getLetterGrade(), null)); 461 462 setAttributeIfValueIsNotNull(root, "enrolled-section", getSectionXmlAttributeValue(student.getEnrolledSection())); 463 } 464 465 private static void setAttributeIfValueIsNotNull(Element root, String name, String value) { 466 if (value != null) { 467 root.setAttribute(name, value); 468 } 469 } 470 471 private static Document createXmlDocument() { 472 Document doc = null; 473 474 try { 475 DocumentBuilderFactory factory = 476 DocumentBuilderFactory.newInstance(); 477 factory.setValidating(true); 478 479 DocumentBuilder builder = factory.newDocumentBuilder(); 480 481 DOMImplementation dom = 482 builder.getDOMImplementation(); 483 DocumentType docType = 484 dom.createDocumentType("student", publicID, systemID); 485 doc = dom.createDocument(null, "student", docType); 486 487 } catch (ParserConfigurationException | DOMException ex) { 488 ex.printStackTrace(System.err); 489 System.exit(1); 490 } 491 return doc; 492 } 493 494 private static Element appendTextElementIfValueIsNotNull(Element parent, String elementName, Object textValue) { 495 if (textValue != null) { 496 Document doc = parent.getOwnerDocument(); 497 Element id = doc.createElement(elementName); 498 id.appendChild(doc.createTextNode(String.valueOf(textValue))); 499 parent.appendChild(id); 500 return id; 501 502 } else { 503 return null; 504 } 505 } 506 507}