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