001package org.cpsolver.studentsct;
002
003import java.io.File;
004import java.io.FileOutputStream;
005import java.io.IOException;
006import java.math.RoundingMode;
007import java.text.DecimalFormat;
008import java.text.DecimalFormatSymbols;
009import java.util.BitSet;
010import java.util.Date;
011import java.util.Locale;
012import java.util.Map;
013import java.util.Set;
014import java.util.TreeSet;
015
016import org.cpsolver.coursett.IdConvertor;
017import org.cpsolver.coursett.model.RoomLocation;
018import org.cpsolver.coursett.model.TimeLocation;
019import org.cpsolver.ifs.solver.Solver;
020import org.cpsolver.ifs.util.Progress;
021import org.cpsolver.studentsct.constraint.LinkedSections;
022import org.cpsolver.studentsct.model.AcademicAreaCode;
023import org.cpsolver.studentsct.model.AreaClassificationMajor;
024import org.cpsolver.studentsct.model.Choice;
025import org.cpsolver.studentsct.model.Config;
026import org.cpsolver.studentsct.model.Course;
027import org.cpsolver.studentsct.model.CourseRequest;
028import org.cpsolver.studentsct.model.Enrollment;
029import org.cpsolver.studentsct.model.FreeTimeRequest;
030import org.cpsolver.studentsct.model.Instructor;
031import org.cpsolver.studentsct.model.Offering;
032import org.cpsolver.studentsct.model.Request;
033import org.cpsolver.studentsct.model.RequestGroup;
034import org.cpsolver.studentsct.model.Section;
035import org.cpsolver.studentsct.model.Student;
036import org.cpsolver.studentsct.model.Subpart;
037import org.cpsolver.studentsct.model.Unavailability;
038import org.cpsolver.studentsct.reservation.CourseReservation;
039import org.cpsolver.studentsct.reservation.CurriculumReservation;
040import org.cpsolver.studentsct.reservation.DummyReservation;
041import org.cpsolver.studentsct.reservation.GroupReservation;
042import org.cpsolver.studentsct.reservation.IndividualReservation;
043import org.cpsolver.studentsct.reservation.LearningCommunityReservation;
044import org.cpsolver.studentsct.reservation.Reservation;
045import org.cpsolver.studentsct.reservation.ReservationOverride;
046import org.dom4j.Document;
047import org.dom4j.DocumentHelper;
048import org.dom4j.Element;
049import org.dom4j.io.OutputFormat;
050import org.dom4j.io.XMLWriter;
051
052
053/**
054 * Save student sectioning solution into an XML file.
055 * 
056 * <br>
057 * <br>
058 * Parameters:
059 * <table border='1' summary='Related Solver Parameters'>
060 * <tr>
061 * <th>Parameter</th>
062 * <th>Type</th>
063 * <th>Comment</th>
064 * </tr>
065 * <tr>
066 * <td>General.Output</td>
067 * <td>{@link String}</td>
068 * <td>Folder with the output solution in XML format (solution.xml)</td>
069 * </tr>
070 * <tr>
071 * <td>Xml.ConvertIds</td>
072 * <td>{@link Boolean}</td>
073 * <td>If true, ids are converted (to be able to make input data public)</td>
074 * </tr>
075 * <tr>
076 * <td>Xml.ShowNames</td>
077 * <td>{@link Boolean}</td>
078 * <td>If false, names are not exported (to be able to make input data public)</td>
079 * </tr>
080 * <tr>
081 * <td>Xml.SaveBest</td>
082 * <td>{@link Boolean}</td>
083 * <td>If true, best solution is saved.</td>
084 * </tr>
085 * <tr>
086 * <td>Xml.SaveInitial</td>
087 * <td>{@link Boolean}</td>
088 * <td>If true, initial solution is saved.</td>
089 * </tr>
090 * <tr>
091 * <td>Xml.SaveCurrent</td>
092 * <td>{@link Boolean}</td>
093 * <td>If true, current solution is saved.</td>
094 * </tr>
095 * <tr>
096 * <td>Xml.SaveOnlineSectioningInfo</td>
097 * <td>{@link Boolean}</td>
098 * <td>If true, save online sectioning info (i.e., expected and held space of
099 * each section)</td>
100 * </tr>
101 * <tr>
102 * <td>Xml.SaveStudentInfo</td>
103 * <td>{@link Boolean}</td>
104 * <td>If true, save student information (i.e., academic area classification,
105 * major, minor)</td>
106 * </tr>
107 * </table>
108 * <br>
109 * <br>
110 * Usage:
111 * <pre><code>
112 * new StudentSectioningXMLSaver(solver).save(new File("solution.xml")); 
113 * </code></pre>
114 * 
115 * @version StudentSct 1.3 (Student Sectioning)<br>
116 *          Copyright (C) 2007 - 2014 Tomas Muller<br>
117 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
118 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
119 * <br>
120 *          This library is free software; you can redistribute it and/or modify
121 *          it under the terms of the GNU Lesser General Public License as
122 *          published by the Free Software Foundation; either version 3 of the
123 *          License, or (at your option) any later version. <br>
124 * <br>
125 *          This library is distributed in the hope that it will be useful, but
126 *          WITHOUT ANY WARRANTY; without even the implied warranty of
127 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
128 *          Lesser General Public License for more details. <br>
129 * <br>
130 *          You should have received a copy of the GNU Lesser General Public
131 *          License along with this library; if not see
132 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
133 */
134
135public class StudentSectioningXMLSaver extends StudentSectioningSaver {
136    private static org.apache.log4j.Logger sLogger = org.apache.log4j.Logger.getLogger(StudentSectioningXMLSaver.class);
137    private static DecimalFormat[] sDF = { new DecimalFormat(""), new DecimalFormat("0"), new DecimalFormat("00"),
138            new DecimalFormat("000"), new DecimalFormat("0000"), new DecimalFormat("00000"),
139            new DecimalFormat("000000"), new DecimalFormat("0000000") };
140    private static DecimalFormat sStudentWeightFormat = new DecimalFormat("0.0000", new DecimalFormatSymbols(Locale.US));
141    private File iOutputFolder = null;
142
143    private boolean iSaveBest = false;
144    private boolean iSaveInitial = false;
145    private boolean iSaveCurrent = false;
146    private boolean iSaveOnlineSectioningInfo = false;
147    private boolean iSaveStudentInfo = true;
148
149    private boolean iConvertIds = false;
150    private boolean iShowNames = false;
151    
152    static {
153        sStudentWeightFormat.setRoundingMode(RoundingMode.DOWN);
154    }
155
156    /**
157     * Constructor
158     * 
159     * @param solver
160     *            student sectioning solver
161     */
162    public StudentSectioningXMLSaver(Solver<Request, Enrollment> solver) {
163        super(solver);
164        iOutputFolder = new File(getModel().getProperties().getProperty("General.Output",
165                "." + File.separator + "output"));
166        iSaveBest = getModel().getProperties().getPropertyBoolean("Xml.SaveBest", true);
167        iSaveInitial = getModel().getProperties().getPropertyBoolean("Xml.SaveInitial", true);
168        iSaveCurrent = getModel().getProperties().getPropertyBoolean("Xml.SaveCurrent", false);
169        iSaveOnlineSectioningInfo = getModel().getProperties().getPropertyBoolean("Xml.SaveOnlineSectioningInfo", true);
170        iSaveStudentInfo = getModel().getProperties().getPropertyBoolean("Xml.SaveStudentInfo", true);
171        iShowNames = getModel().getProperties().getPropertyBoolean("Xml.ShowNames", true);
172        iConvertIds = getModel().getProperties().getPropertyBoolean("Xml.ConvertIds", false);
173    }
174
175    /** Convert bitset to a bit string */
176    private static String bitset2string(BitSet b) {
177        StringBuffer sb = new StringBuffer();
178        for (int i = 0; i < b.length(); i++)
179            sb.append(b.get(i) ? "1" : "0");
180        return sb.toString();
181    }
182
183    /** Generate id for given object with the given id */
184    private String getId(String type, String id) {
185        if (!iConvertIds)
186            return id.toString();
187        return IdConvertor.getInstance().convert(type, id);
188    }
189
190    /** Generate id for given object with the given id */
191    private String getId(String type, Number id) {
192        return getId(type, id.toString());
193    }
194
195    /** Generate id for given object with the given id */
196    private String getId(String type, long id) {
197        return getId(type, String.valueOf(id));
198    }
199
200    /** Save an XML file */
201    @Override
202    public void save() throws Exception {
203        save(null);
204    }
205
206    /**
207     * Save an XML file
208     * 
209     * @param outFile
210     *            output file
211     * @throws Exception thrown when the save fails
212     */
213    public void save(File outFile) throws Exception {
214        if (outFile == null) {
215            outFile = new File(iOutputFolder, "solution.xml");
216        } else if (outFile.getParentFile() != null) {
217            outFile.getParentFile().mkdirs();
218        }
219        sLogger.debug("Writting XML data to:" + outFile);
220
221        Document document = DocumentHelper.createDocument();
222        document.addComment("Student Sectioning");
223        
224        populate(document);
225
226        FileOutputStream fos = null;
227        try {
228            fos = new FileOutputStream(outFile);
229            (new XMLWriter(fos, OutputFormat.createPrettyPrint())).write(document);
230            fos.flush();
231            fos.close();
232            fos = null;
233        } finally {
234            try {
235                if (fos != null)
236                    fos.close();
237            } catch (IOException e) {
238            }
239        }
240
241        if (iConvertIds)
242            IdConvertor.getInstance().save();
243    }
244    
245    public Document saveDocument() {
246        Document document = DocumentHelper.createDocument();
247        document.addComment("Student Sectioning");
248        
249        populate(document);
250
251        return document;
252    }
253
254    /**
255     * Fill in all the data into the given document
256     * @param document document to be populated
257     */
258    protected void populate(Document document) {
259        if (iSaveCurrent || iSaveBest) {
260            StringBuffer comments = new StringBuffer("Solution Info:\n");
261            Map<String, String> solutionInfo = (getSolution() == null ? getModel().getExtendedInfo(getAssignment()) : getSolution().getExtendedInfo());
262            for (String key : new TreeSet<String>(solutionInfo.keySet())) {
263                String value = solutionInfo.get(key);
264                comments.append("    " + key + ": " + value + "\n");
265            }
266            document.addComment(comments.toString());
267        }
268
269        Element root = document.addElement("sectioning");
270        root.addAttribute("version", "1.0");
271        root.addAttribute("initiative", getModel().getProperties().getProperty("Data.Initiative"));
272        root.addAttribute("term", getModel().getProperties().getProperty("Data.Term"));
273        root.addAttribute("year", getModel().getProperties().getProperty("Data.Year"));
274        root.addAttribute("created", String.valueOf(new Date()));
275
276        saveOfferings(root);
277
278        saveStudents(root);
279        
280        saveLinkedSections(root);
281        
282        saveTravelTimes(root);
283
284        if (iShowNames) {
285            Progress.getInstance(getModel()).save(root);
286        }
287    }
288    
289    /**
290     * Save offerings
291     * @param root document root
292     */
293    protected void saveOfferings(Element root) {
294        Element offeringsEl = root.addElement("offerings");
295        for (Offering offering : getModel().getOfferings()) {
296            Element offeringEl = offeringsEl.addElement("offering");
297            saveOffering(offeringEl, offering);
298            saveReservations(offeringEl, offering);
299        }
300    }
301    
302    /**
303     * Save given offering
304     * @param offeringEl offering element to be populated
305     * @param offering offering to be saved
306     */
307    protected void saveOffering(Element offeringEl, Offering offering) {
308        offeringEl.addAttribute("id", getId("offering", offering.getId()));
309        if (iShowNames)
310            offeringEl.addAttribute("name", offering.getName());
311        for (Course course : offering.getCourses()) {
312            Element courseEl = offeringEl.addElement("course");
313            saveCourse(courseEl, course);
314        }
315        for (Config config : offering.getConfigs()) {
316            Element configEl = offeringEl.addElement("config");
317            saveConfig(configEl, config);
318        }
319    }
320    
321    /**
322     * Save given course
323     * @param courseEl course element to be populated
324     * @param course course to be saved
325     */
326    protected void saveCourse(Element courseEl, Course course) {
327        courseEl.addAttribute("id", getId("course", course.getId()));
328        if (iShowNames)
329            courseEl.addAttribute("subjectArea", course.getSubjectArea());
330        if (iShowNames)
331            courseEl.addAttribute("courseNbr", course.getCourseNumber());
332        if (iShowNames && course.getLimit() >= 0)
333            courseEl.addAttribute("limit", String.valueOf(course.getLimit()));
334        if (iShowNames && course.getProjected() != 0)
335            courseEl.addAttribute("projected", String.valueOf(course.getProjected()));
336        if (iShowNames && course.getCredit() != null)
337            courseEl.addAttribute("credit", course.getCredit());
338        if (course.hasCreditValue())
339            courseEl.addAttribute("credits", course.getCreditValue().toString());
340    }
341    
342    /**
343     * Save given config
344     * @param configEl config element to be populated
345     * @param config config to be saved
346     */
347    protected void saveConfig(Element configEl, Config config) {
348        configEl.addAttribute("id", getId("config", config.getId()));
349        if (config.getLimit() >= 0)
350            configEl.addAttribute("limit", String.valueOf(config.getLimit()));
351        if (iShowNames)
352            configEl.addAttribute("name", config.getName());
353        for (Subpart subpart : config.getSubparts()) {
354            Element subpartEl = configEl.addElement("subpart");
355            saveSubpart(subpartEl, subpart);
356        }
357        if (config.getInstructionalMethodId() != null) {
358            Element imEl = configEl.addElement("instructional-method");
359            imEl.addAttribute("id", getId("instructional-method", config.getInstructionalMethodId()));
360            if (iShowNames && config.getInstructionalMethodName() != null)
361                imEl.addAttribute("name", config.getInstructionalMethodName());
362            if (iShowNames && config.getInstructionalMethodReference() != null)
363                imEl.addAttribute("reference", config.getInstructionalMethodReference());
364        }
365    }
366    
367    /**
368     * Save scheduling subpart
369     * @param subpartEl subpart element to be populated
370     * @param subpart subpart to be saved
371     */
372    protected void saveSubpart(Element subpartEl, Subpart subpart) {
373        subpartEl.addAttribute("id", getId("subpart", subpart.getId()));
374        subpartEl.addAttribute("itype", subpart.getInstructionalType());
375        if (subpart.getParent() != null)
376            subpartEl.addAttribute("parent", getId("subpart", subpart.getParent().getId()));
377        if (iShowNames) {
378            subpartEl.addAttribute("name", subpart.getName());
379            if (subpart.getCredit() != null)
380                subpartEl.addAttribute("credit", subpart.getCredit());
381            if (subpart.hasCreditValue())
382                subpartEl.addAttribute("credits", subpart.getCreditValue().toString());
383        }
384        if (subpart.isAllowOverlap())
385            subpartEl.addAttribute("allowOverlap", "true");
386        for (Section section : subpart.getSections()) {
387            Element sectionEl = subpartEl.addElement("section");
388            saveSection(sectionEl, section);
389        }
390    }
391    
392    /**
393     * Save section
394     * @param sectionEl section element to be populated
395     * @param section section to be saved
396     */
397    protected void saveSection(Element sectionEl, Section section) {
398        sectionEl.addAttribute("id", getId("section", section.getId()));
399        sectionEl.addAttribute("limit", String.valueOf(section.getLimit()));
400        if (section.isCancelled())
401            sectionEl.addAttribute("cancelled", "true");
402        if (!section.isEnabled())
403            sectionEl.addAttribute("enabled", "false");
404        if (iShowNames && section.getNameByCourse() != null)
405            for (Map.Entry<Long, String> entry: section.getNameByCourse().entrySet())
406                sectionEl.addElement("cname").addAttribute("id", getId("course", entry.getKey())).setText(entry.getValue());
407        if (section.getParent() != null)
408            sectionEl.addAttribute("parent", getId("section", section.getParent().getId()));
409        if (section.hasInstructors()) {
410            for (Instructor instructor: section.getInstructors()) {
411                Element instructorEl = sectionEl.addElement("instructor");
412                instructorEl.addAttribute("id", getId("instructor", instructor.getId()));
413                if (iShowNames && instructor.getName() != null)
414                    instructorEl.addAttribute("name", instructor.getName());
415                if (iShowNames && instructor.getExternalId() != null)
416                    instructorEl.addAttribute("externalId", instructor.getExternalId());
417                if (iShowNames && instructor.getEmail() != null)
418                    instructorEl.addAttribute("email", instructor.getExternalId());
419            }
420        }
421        if (iShowNames)
422            sectionEl.addAttribute("name", section.getName());
423        if (section.getPlacement() != null) {
424            TimeLocation tl = section.getPlacement().getTimeLocation();
425            if (tl != null) {
426                Element timeLocationEl = sectionEl.addElement("time");
427                timeLocationEl.addAttribute("days", sDF[7].format(Long.parseLong(Integer
428                        .toBinaryString(tl.getDayCode()))));
429                timeLocationEl.addAttribute("start", String.valueOf(tl.getStartSlot()));
430                timeLocationEl.addAttribute("length", String.valueOf(tl.getLength()));
431                if (tl.getBreakTime() != 0)
432                    timeLocationEl.addAttribute("breakTime", String.valueOf(tl.getBreakTime()));
433                if (iShowNames && tl.getTimePatternId() != null)
434                    timeLocationEl.addAttribute("pattern", getId("timePattern", tl.getTimePatternId()));
435                if (iShowNames && tl.getDatePatternId() != null)
436                    timeLocationEl.addAttribute("datePattern", tl.getDatePatternId().toString());
437                if (iShowNames && tl.getDatePatternName() != null
438                        && tl.getDatePatternName().length() > 0)
439                    timeLocationEl.addAttribute("datePatternName", tl.getDatePatternName());
440                timeLocationEl.addAttribute("dates", bitset2string(tl.getWeekCode()));
441                if (iShowNames)
442                    timeLocationEl.setText(tl.getLongName(true));
443            }
444            for (RoomLocation rl : section.getRooms()) {
445                Element roomLocationEl = sectionEl.addElement("room");
446                roomLocationEl.addAttribute("id", getId("room", rl.getId()));
447                if (iShowNames && rl.getBuildingId() != null)
448                    roomLocationEl.addAttribute("building", getId("building", rl.getBuildingId()));
449                if (iShowNames && rl.getName() != null)
450                    roomLocationEl.addAttribute("name", rl.getName());
451                roomLocationEl.addAttribute("capacity", String.valueOf(rl.getRoomSize()));
452                if (rl.getPosX() != null && rl.getPosY() != null)
453                    roomLocationEl.addAttribute("location", rl.getPosX() + "," + rl.getPosY());
454                if (rl.getIgnoreTooFar())
455                    roomLocationEl.addAttribute("ignoreTooFar", "true");
456            }
457        }
458        if (iSaveOnlineSectioningInfo) {
459            if (section.getSpaceHeld() != 0.0)
460                sectionEl.addAttribute("hold", sStudentWeightFormat.format(section.getSpaceHeld()));
461            if (section.getSpaceExpected() != 0.0)
462                sectionEl.addAttribute("expect", sStudentWeightFormat
463                        .format(section.getSpaceExpected()));
464        }
465        if (section.getIgnoreConflictWithSectionIds() != null && !section.getIgnoreConflictWithSectionIds().isEmpty()) {
466            Element ignoreEl = sectionEl.addElement("no-conflicts");
467            for (Long sectionId: section.getIgnoreConflictWithSectionIds())
468                ignoreEl.addElement("section").addAttribute("id", getId("section", sectionId));
469        }
470    }
471    
472    /**
473     * Save reservations of the given offering
474     * @param offeringEl offering element to be populated with reservations
475     * @param offering offering which reservations are to be saved
476     */
477    protected void saveReservations(Element offeringEl, Offering offering) {
478        if (!offering.getReservations().isEmpty()) {
479            for (Reservation r: offering.getReservations()) {
480                saveReservation(offeringEl.addElement("reservation"), r);
481            }
482        }
483    }
484    
485    /**
486     * Save reservation
487     * @param reservationEl reservation element to be populated
488     * @param reservation reservation to be saved
489     */
490    protected void saveReservation(Element reservationEl, Reservation reservation) {
491        reservationEl.addAttribute("id", getId("reservation", reservation.getId()));
492        reservationEl.addAttribute("expired", reservation.isExpired() ? "true" : "false");
493        if (reservation instanceof LearningCommunityReservation) {
494            LearningCommunityReservation lc = (LearningCommunityReservation)reservation;
495            reservationEl.addAttribute("type", "lc");
496            for (Long studentId: lc.getStudentIds())
497                reservationEl.addElement("student").addAttribute("id", getId("student", studentId));
498            if (lc.getReservationLimit() >= 0.0)
499                reservationEl.addAttribute("limit", String.valueOf(lc.getReservationLimit()));
500            reservationEl.addAttribute("course", getId("course",lc.getCourse().getId()));
501        } else if (reservation instanceof GroupReservation) {
502            GroupReservation gr = (GroupReservation)reservation;
503            reservationEl.addAttribute("type", "group");
504            for (Long studentId: gr.getStudentIds())
505                reservationEl.addElement("student").addAttribute("id", getId("student", studentId));
506            if (gr.getReservationLimit() >= 0.0)
507                reservationEl.addAttribute("limit", String.valueOf(gr.getReservationLimit()));
508        } else if (reservation instanceof ReservationOverride) {
509            reservationEl.addAttribute("type", "override");
510            ReservationOverride o = (ReservationOverride)reservation;
511            for (Long studentId: o.getStudentIds())
512                reservationEl.addElement("student").addAttribute("id", getId("student", studentId));
513        } else if (reservation instanceof IndividualReservation) {
514            reservationEl.addAttribute("type", "individual");
515            for (Long studentId: ((IndividualReservation)reservation).getStudentIds())
516                reservationEl.addElement("student").addAttribute("id", getId("student", studentId));
517        } else if (reservation instanceof CurriculumReservation) {
518            reservationEl.addAttribute("type", "curriculum");
519            CurriculumReservation cr = (CurriculumReservation)reservation;
520            if (cr.getReservationLimit() >= 0.0)
521                reservationEl.addAttribute("limit", String.valueOf(cr.getReservationLimit()));
522            reservationEl.addAttribute("area", cr.getAcademicArea());
523            for (String clasf: cr.getClassifications())
524                reservationEl.addElement("classification").addAttribute("code", clasf);
525            for (String major: cr.getMajors())
526                reservationEl.addElement("major").addAttribute("code", major);
527        } else if (reservation instanceof CourseReservation) {
528            reservationEl.addAttribute("type", "course");
529            CourseReservation cr = (CourseReservation)reservation;
530            reservationEl.addAttribute("course", getId("course",cr.getCourse().getId()));
531        } else if (reservation instanceof DummyReservation) {
532            reservationEl.addAttribute("type", "dummy");
533        }
534        reservationEl.addAttribute("priority", String.valueOf(reservation.getPriority()));
535        reservationEl.addAttribute("mustBeUsed", reservation.mustBeUsed() ? "true" : "false");
536        reservationEl.addAttribute("allowOverlap", reservation.isAllowOverlap() ? "true" : "false");
537        reservationEl.addAttribute("canAssignOverLimit", reservation.canAssignOverLimit() ? "true" : "false");
538        reservationEl.addAttribute("allowDisabled", reservation.isAllowDisabled() ? "true" : "false");
539        for (Config config: reservation.getConfigs())
540            reservationEl.addElement("config").addAttribute("id", getId("config", config.getId()));
541        for (Map.Entry<Subpart, Set<Section>> entry: reservation.getSections().entrySet()) {
542            for (Section section: entry.getValue()) {
543                reservationEl.addElement("section").addAttribute("id", getId("section", section.getId()));
544            }
545        }
546    }
547    
548    /**
549     * Save students
550     * @param root document root
551     */
552    protected void saveStudents(Element root) {
553        Element studentsEl = root.addElement("students");
554        for (Student student : getModel().getStudents()) {
555            Element studentEl = studentsEl.addElement("student");
556            saveStudent(studentEl, student);
557            for (Request request : student.getRequests()) {
558                saveRequest(studentEl, request);
559            }
560        }
561    }
562    
563    /**
564     * Save student
565     * @param studentEl student element to be populated
566     * @param student student to be saved
567     */
568    protected void saveStudent(Element studentEl, Student student) {
569        studentEl.addAttribute("id", getId("student", student.getId()));
570        if (iShowNames) {
571            if (student.getExternalId() != null && !student.getExternalId().isEmpty())
572                studentEl.addAttribute("externalId", student.getExternalId());
573            if (student.getName() != null && !student.getName().isEmpty())
574                studentEl.addAttribute("name", student.getName());
575            if (student.getStatus() != null && !student.getStatus().isEmpty())
576                studentEl.addAttribute("status", student.getStatus());
577        }
578        if (student.isDummy())
579            studentEl.addAttribute("dummy", "true");
580        if (student.isNeedShortDistances())
581            studentEl.addAttribute("shortDistances", "true");
582        if (student.isAllowDisabled())
583            studentEl.addAttribute("allowDisabled", "true");
584        if (student.hasMinCredit())
585            studentEl.addAttribute("minCredit", String.valueOf(student.getMinCredit()));
586        if (student.hasMaxCredit())
587            studentEl.addAttribute("maxCredit", String.valueOf(student.getMaxCredit()));
588        if (iSaveStudentInfo) {
589            for (AcademicAreaCode aac : student.getAcademicAreaClasiffications()) {
590                Element aacEl = studentEl.addElement("classification");
591                if (aac.getArea() != null)
592                    aacEl.addAttribute("area", aac.getArea());
593                if (aac.getCode() != null)
594                    aacEl.addAttribute("code", aac.getCode());
595                if (aac.getLabel() != null)
596                    aacEl.addAttribute("label", aac.getLabel());
597            }
598            for (AcademicAreaCode aac : student.getMajors()) {
599                Element aacEl = studentEl.addElement("major");
600                if (aac.getArea() != null)
601                    aacEl.addAttribute("area", aac.getArea());
602                if (aac.getCode() != null)
603                    aacEl.addAttribute("code", aac.getCode());
604                if (aac.getLabel() != null)
605                    aacEl.addAttribute("label", aac.getLabel());
606            }
607            for (AcademicAreaCode aac : student.getMinors()) {
608                Element aacEl = studentEl.addElement("minor");
609                if (aac.getArea() != null)
610                    aacEl.addAttribute("area", aac.getArea());
611                if (aac.getCode() != null)
612                    aacEl.addAttribute("code", aac.getCode());
613                if (aac.getLabel() != null)
614                    aacEl.addAttribute("label", aac.getLabel());
615            }
616            for (AreaClassificationMajor acm : student.getAreaClassificationMajors()) {
617                Element acmEl = studentEl.addElement("acm");
618                if (acm.getArea() != null)
619                    acmEl.addAttribute("area", acm.getArea());
620                if (acm.getArea() != null)
621                    acmEl.addAttribute("classification", acm.getClassification());
622                if (acm.getArea() != null)
623                    acmEl.addAttribute("major", acm.getMajor());
624            }
625        }
626        for (Unavailability unavailability: student.getUnavailabilities()) {
627            Element unavEl = studentEl.addElement("unavailability");
628            unavEl.addAttribute("offering", getId("offering", unavailability.getSection().getSubpart().getConfig().getOffering().getId()));
629            unavEl.addAttribute("section", getId("section", unavailability.getSection().getId()));
630            if (unavailability.isAllowOverlap()) unavEl.addAttribute("allowOverlap", "true");
631        }
632    }
633    
634    /**
635     * Save request
636     * @param studentEl student element to be populated
637     * @param request request to be saved
638     */
639    protected void saveRequest(Element studentEl, Request request) {
640        if (request instanceof FreeTimeRequest) {
641            saveFreeTimeRequest(studentEl.addElement("freeTime"), (FreeTimeRequest) request);
642        } else if (request instanceof CourseRequest) {
643            saveCourseRequest(studentEl.addElement("course"), (CourseRequest) request);
644        }
645    }
646    
647    /**
648     * Save free time request
649     * @param requestEl request element to be populated
650     * @param request free time request to be saved 
651     */
652    protected void saveFreeTimeRequest(Element requestEl, FreeTimeRequest request) {
653        requestEl.addAttribute("id", getId("request", request.getId()));
654        requestEl.addAttribute("priority", String.valueOf(request.getPriority()));
655        if (request.isAlternative())
656            requestEl.addAttribute("alternative", "true");
657        if (request.getWeight() != 1.0)
658            requestEl.addAttribute("weight", sStudentWeightFormat.format(request.getWeight()));
659        TimeLocation tl = request.getTime();
660        if (tl != null) {
661            requestEl.addAttribute("days", sDF[7].format(Long.parseLong(Integer.toBinaryString(tl
662                    .getDayCode()))));
663            requestEl.addAttribute("start", String.valueOf(tl.getStartSlot()));
664            requestEl.addAttribute("length", String.valueOf(tl.getLength()));
665            if (iShowNames && tl.getDatePatternId() != null)
666                requestEl.addAttribute("datePattern", tl.getDatePatternId().toString());
667            requestEl.addAttribute("dates", bitset2string(tl.getWeekCode()));
668            if (iShowNames)
669                requestEl.setText(tl.getLongName(true));
670        }
671        if (iSaveInitial && request.getInitialAssignment() != null) {
672            requestEl.addElement("initial");
673        }
674        if (iSaveCurrent && getAssignment().getValue(request) != null) {
675            requestEl.addElement("current");
676        }
677        if (iSaveBest && request.getBestAssignment() != null) {
678            requestEl.addElement("best");
679        }
680    }
681    
682    /**
683     * Save course request 
684     * @param requestEl request element to be populated
685     * @param request course request to be saved
686     */
687    protected void saveCourseRequest(Element requestEl, CourseRequest request) {
688        requestEl.addAttribute("id", getId("request", request.getId()));
689        requestEl.addAttribute("priority", String.valueOf(request.getPriority()));
690        if (request.isAlternative())
691            requestEl.addAttribute("alternative", "true");
692        if (request.getWeight() != 1.0)
693            requestEl.addAttribute("weight", sStudentWeightFormat.format(request.getWeight()));
694        requestEl.addAttribute("waitlist", request.isWaitlist() ? "true" : "false");
695        if (request.isCritical())
696            requestEl.addAttribute("critical", "true");
697        if (request.getTimeStamp() != null)
698            requestEl.addAttribute("timeStamp", request.getTimeStamp().toString());
699        boolean first = true;
700        for (Course course : request.getCourses()) {
701            if (first)
702                requestEl.addAttribute("course", getId("course", course.getId()));
703            else
704                requestEl.addElement("alternative").addAttribute("course", getId("course", course.getId()));
705            first = false;
706        }
707        for (Choice choice : request.getWaitlistedChoices()) {
708            Element choiceEl = requestEl.addElement("waitlisted");
709            choiceEl.addAttribute("offering", getId("offering", choice.getOffering().getId()));
710            choiceEl.setText(choice.getId());
711        }
712        for (Choice choice : request.getSelectedChoices()) {
713            Element choiceEl = requestEl.addElement("selected");
714            choiceEl.addAttribute("offering", getId("offering", choice.getOffering().getId()));
715            choiceEl.setText(choice.getId());
716        }
717        for (Choice choice : request.getRequiredChoices()) {
718            Element choiceEl = requestEl.addElement("required");
719            choiceEl.addAttribute("offering", getId("offering", choice.getOffering().getId()));
720            choiceEl.setText(choice.getId());
721        }
722        if (iSaveInitial && request.getInitialAssignment() != null) {
723            saveEnrollment(requestEl.addElement("initial"), request.getInitialAssignment());
724        }
725        if (iSaveCurrent && getAssignment().getValue(request) != null) {
726            saveEnrollment(requestEl.addElement("current"), getAssignment().getValue(request));
727        }
728        if (iSaveBest && request.getBestAssignment() != null) {
729            saveEnrollment(requestEl.addElement("best"), request.getBestAssignment());
730        }
731        for (RequestGroup g: request.getRequestGroups()) {
732            Element groupEl = requestEl.addElement("group").addAttribute("id", getId("group", g.getId())).addAttribute("course", getId("course", g.getCourse().getId()));
733            if (iShowNames)
734                groupEl.addAttribute("name", g.getName());
735        }
736    }
737    
738    /**
739     * Save enrollment
740     * @param assignmentEl assignment element to be populated
741     * @param enrollment enrollment to be saved
742     */
743    protected void saveEnrollment(Element assignmentEl, Enrollment enrollment) {
744        if (enrollment.getReservation() != null)
745            assignmentEl.addAttribute("reservation", getId("reservation", enrollment.getReservation().getId()));
746        for (Section section : enrollment.getSections()) {
747            Element sectionEl = assignmentEl.addElement("section").addAttribute("id",
748                    getId("section", section.getId()));
749            if (iShowNames)
750                sectionEl.setText(section.getName() + " " +
751                        (section.getTime() == null ? " Arr Hrs" : " " + section.getTime().getLongName(true)) +
752                        (section.getNrRooms() == 0 ? "" : " " + section.getPlacement().getRoomName(",")) +
753                        (section.hasInstructors() ? " " + section.getInstructorNames(",") : ""));
754        }
755    }
756    
757    /**
758     * Save linked sections
759     * @param root document root
760     */
761    protected void saveLinkedSections(Element root) {
762        Element constrainstEl = root.addElement("constraints");
763        for (LinkedSections linkedSections: getModel().getLinkedSections()) {
764            Element linkEl = constrainstEl.addElement("linked-sections");
765            linkEl.addAttribute("mustBeUsed", linkedSections.isMustBeUsed() ? "true" : "false");
766            for (Offering offering: linkedSections.getOfferings())
767                for (Subpart subpart: linkedSections.getSubparts(offering))
768                    for (Section section: linkedSections.getSections(subpart))
769                        linkEl.addElement("section")
770                            .addAttribute("offering", getId("offering", offering.getId()))
771                            .addAttribute("id", getId("section", section.getId()));
772        }
773    }
774    
775    /**
776     * Save travel times
777     * @param root document root
778     */
779    protected void saveTravelTimes(Element root) {
780        if (getModel().getDistanceMetric() != null) {
781            Map<Long, Map<Long, Integer>> travelTimes = getModel().getDistanceMetric().getTravelTimes();
782            if (travelTimes != null) {
783                Element travelTimesEl = root.addElement("travel-times");
784                for (Map.Entry<Long, Map<Long, Integer>> e1: travelTimes.entrySet())
785                    for (Map.Entry<Long, Integer> e2: e1.getValue().entrySet())
786                        travelTimesEl.addElement("travel-time")
787                            .addAttribute("id1", getId("room", e1.getKey().toString()))
788                            .addAttribute("id2", getId("room", e2.getKey().toString()))
789                            .addAttribute("minutes", e2.getValue().toString());
790            }
791        }
792    }
793}