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