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