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