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