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