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