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