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