    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.getRequestPriority() != RequestPriority.Normal)
    828            requestEl.addAttribute("importance", request.getRequestPriority().name());
    829        if (request.isAlternative())
    830            requestEl.addAttribute("alternative", "true");
    831        if (request.getWeight() != 1.0)
    832            requestEl.addAttribute("weight", sStudentWeightFormat.format(request.getWeight()));
    833        TimeLocation tl = request.getTime();
    834        if (tl != null) {
    835            requestEl.addAttribute("days", sDF[7].format(Long.parseLong(Integer.toBinaryString(tl
    836                    .getDayCode()))));
    837            requestEl.addAttribute("start", String.valueOf(tl.getStartSlot()));
    838            requestEl.addAttribute("length", String.valueOf(tl.getLength()));
    839            if (iShowNames && tl.getDatePatternId() != null)
    840                requestEl.addAttribute("datePattern", tl.getDatePatternId().toString());
    841            requestEl.addAttribute("dates", bitset2string(tl.getWeekCode()));
    842            if (iShowNames)
    843                requestEl.setText(tl.getLongName(true));
    844        }
    845        if (iSaveInitial && request.getInitialAssignment() != null) {
    846            requestEl.addElement("initial");
    847        }
    848        if (iSaveCurrent && getAssignment().getValue(request) != null) {
    849            requestEl.addElement("current");
    850        }
    851        if (iSaveBest && request.getBestAssignment() != null) {
    852            requestEl.addElement("best");
    853        }
    854    }
    855
    856    /**
    857     * Save course request
    858     * @param requestEl request element to be populated
    859     * @param request course request to be saved
    860     */
    861    protected void saveCourseRequest(Element requestEl, CourseRequest request) {
    862        requestEl.addAttribute("id", getId("request", request.getId()));
    863        requestEl.addAttribute("priority", String.valueOf(request.getPriority()));
    864        if (request.isAlternative())
    865            requestEl.addAttribute("alternative", "true");
    866        if (request.getWeight() != 1.0)
    867            requestEl.addAttribute("weight", sStudentWeightFormat.format(request.getWeight()));
    868        requestEl.addAttribute("waitlist", request.isWaitlist() ? "true" : "false");
    869        if (request.getRequestPriority() != RequestPriority.Normal)
    870            requestEl.addAttribute("importance", request.getRequestPriority().name());
    871        if (request.getRequestPriority() == RequestPriority.Critical)
    872            requestEl.addAttribute("critical", "true");
    873        if (request.getTimeStamp() != null)
    874            requestEl.addAttribute("timeStamp", request.getTimeStamp().toString());
    875        boolean first = true;
    876        for (Course course : request.getCourses()) {
    877            if (first)
    878                requestEl.addAttribute("course", getId("course", course.getId()));
    879            else
    880                requestEl.addElement("alternative").addAttribute("course", getId("course", course.getId()));
    881            first = false;
    882        }
    883        for (Choice choice : request.getWaitlistedChoices()) {
    884            Element choiceEl = requestEl.addElement("waitlisted");
    885            choiceEl.addAttribute("offering", getId("offering", choice.getOffering().getId()));
    886            choiceEl.setText(choice.getId());
    887        }
    888        for (Choice choice : request.getSelectedChoices()) {
    889            Element choiceEl = requestEl.addElement("selected");
    890            choiceEl.addAttribute("offering", getId("offering", choice.getOffering().getId()));
    891            choiceEl.setText(choice.getId());
    892        }
    893        for (Choice choice : request.getRequiredChoices()) {
    894            Element choiceEl = requestEl.addElement("required");
    895            choiceEl.addAttribute("offering", getId("offering", choice.getOffering().getId()));
    896            choiceEl.setText(choice.getId());
    897        }
    898        if (iSaveInitial && request.getInitialAssignment() != null) {
    899            saveEnrollment(requestEl.addElement("initial"), request.getInitialAssignment());
    900        }
    901        if (iSaveCurrent && getAssignment().getValue(request) != null) {
    902            saveEnrollment(requestEl.addElement("current"), getAssignment().getValue(request));
    903        }
    904        if (iSaveBest && request.getBestAssignment() != null) {
    905            saveEnrollment(requestEl.addElement("best"), request.getBestAssignment());
    906        }
    907        if (request.isFixed())
    908            saveEnrollment(requestEl.addElement("fixed"), request.getFixedValue());
    909        for (RequestGroup g: request.getRequestGroups()) {
    910            Element groupEl = requestEl.addElement("group").addAttribute("id", getId("group", g.getId())).addAttribute("course", getId("course", g.getCourse().getId()));
    911            if (iShowNames)
    912                groupEl.addAttribute("name", g.getName());
    913        }
    914    }
    915
    916    /**
    917     * Save enrollment
    918     * @param assignmentEl assignment element to be populated
    919     * @param enrollment enrollment to be saved
    920     */
    921    protected void saveEnrollment(Element assignmentEl, Enrollment enrollment) {
    922        if (enrollment.getReservation() != null)
    923            assignmentEl.addAttribute("reservation", getId("reservation", enrollment.getReservation().getId()));
    924        if (enrollment.getCourse() != null)
    925            assignmentEl.addAttribute("course", getId("course", enrollment.getCourse().getId()));
    926        for (Section section : enrollment.getSections()) {
    927            Element sectionEl = assignmentEl.addElement("section").addAttribute("id",
    928                    getId("section", section.getId()));
    929            if (iShowNames)
    930                sectionEl.setText(section.getName() + " " +
    931                        (section.getTime() == null ? " Arr Hrs" : " " + section.getTime().getLongName(true)) +
    932                        (section.getNrRooms() == 0 ? "" : " " + section.getPlacement().getRoomName(",")) +
    933                        (section.hasInstructors() ? " " + section.getInstructorNames(",") : ""));
    934        }
    935    }
    936
    937    /**
    938     * Save linked sections
    939     * @param root document root
    940     */
    941    protected void saveLinkedSections(Element root) {
    942        Element constrainstEl = root.addElement("constraints");
    943        for (LinkedSections linkedSections: getModel().getLinkedSections()) {
    944            Element linkEl = constrainstEl.addElement("linked-sections");
    945            linkEl.addAttribute("mustBeUsed", linkedSections.isMustBeUsed() ? "true" : "false");
    946            for (Offering offering: linkedSections.getOfferings())
    947                for (Subpart subpart: linkedSections.getSubparts(offering))
    948                    for (Section section: linkedSections.getSections(subpart))
    949                        linkEl.addElement("section")
    950                            .addAttribute("offering", getId("offering", offering.getId()))
    951                            .addAttribute("id", getId("section", section.getId()));
    952        }
    953    }
    954
    955    /**
    956     * Save travel times
    957     * @param root document root
    958     */
    959    protected void saveTravelTimes(Element root) {
    960        if (getModel().getDistanceMetric() != null) {
    961            Map<Long, Map<Long, Integer>> travelTimes = getModel().getDistanceMetric().getTravelTimes();
    962            if (travelTimes != null) {
    963                Element travelTimesEl = root.addElement("travel-times");
    964                for (Map.Entry<Long, Map<Long, Integer>> e1: travelTimes.entrySet())
    965                    for (Map.Entry<Long, Integer> e2: e1.getValue().entrySet())
    966                        travelTimesEl.addElement("travel-time")
    967                            .addAttribute("id1", getId("room", e1.getKey().toString()))
    968                            .addAttribute("id2", getId("room", e2.getKey().toString()))
    969                            .addAttribute("minutes", e2.getValue().toString());
    970            }
    971        }
    972    }
    973}