    001package net.sf.cpsolver.studentsct;
    002
    003import java.io.File;
    004import java.util.ArrayList;
    005import java.util.BitSet;
    006import java.util.HashSet;
    007import java.util.HashMap;
    008import java.util.Iterator;
    009import java.util.List;
    010import java.util.Map;
    011import java.util.Set;
    012
    013import net.sf.cpsolver.coursett.model.Placement;
    014import net.sf.cpsolver.coursett.model.RoomLocation;
    015import net.sf.cpsolver.coursett.model.TimeLocation;
    016import net.sf.cpsolver.ifs.model.Constraint;
    017import net.sf.cpsolver.ifs.util.DistanceMetric;
    018import net.sf.cpsolver.ifs.util.Progress;
    019import net.sf.cpsolver.studentsct.filter.StudentFilter;
    020import net.sf.cpsolver.studentsct.model.AcademicAreaCode;
    021import net.sf.cpsolver.studentsct.model.Choice;
    022import net.sf.cpsolver.studentsct.model.Config;
    023import net.sf.cpsolver.studentsct.model.Course;
    024import net.sf.cpsolver.studentsct.model.CourseRequest;
    025import net.sf.cpsolver.studentsct.model.Enrollment;
    026import net.sf.cpsolver.studentsct.model.FreeTimeRequest;
    027import net.sf.cpsolver.studentsct.model.Offering;
    028import net.sf.cpsolver.studentsct.model.Request;
    029import net.sf.cpsolver.studentsct.model.Section;
    030import net.sf.cpsolver.studentsct.model.Student;
    031import net.sf.cpsolver.studentsct.model.Subpart;
    032import net.sf.cpsolver.studentsct.reservation.CourseReservation;
    033import net.sf.cpsolver.studentsct.reservation.CurriculumReservation;
    034import net.sf.cpsolver.studentsct.reservation.DummyReservation;
    035import net.sf.cpsolver.studentsct.reservation.GroupReservation;
    036import net.sf.cpsolver.studentsct.reservation.IndividualReservation;
    037import net.sf.cpsolver.studentsct.reservation.Reservation;
    038
    039import org.dom4j.Document;
    040import org.dom4j.DocumentException;
    041import org.dom4j.Element;
    042import org.dom4j.io.SAXReader;
    043
    044/**
    045 * Load student sectioning model from an XML file.
    046 *
    047 * <br>
    048 * <br>
    049 * Parameters:
    050 * <table border='1'>
    051 * <tr>
    052 * <th>Parameter</th>
    053 * <th>Type</th>
    054 * <th>Comment</th>
    055 * </tr>
    056 * <tr>
    057 * <td>General.Input</td>
    058 * <td>{@link String}</td>
    059 * <td>Path of an XML file to be loaded</td>
    060 * </tr>
    061 * <tr>
    062 * <td>Xml.LoadBest</td>
    063 * <td>{@link Boolean}</td>
    064 * <td>If true, load best assignments</td>
    065 * </tr>
    066 * <tr>
    067 * <td>Xml.LoadInitial</td>
    068 * <td>{@link Boolean}</td>
    069 * <td>If false, load initial assignments</td>
    070 * </tr>
    071 * <tr>
    072 * <td>Xml.LoadCurrent</td>
    073 * <td>{@link Boolean}</td>
    074 * <td>If true, load current assignments</td>
    075 * </tr>
    076 * <tr>
    077 * <td>Xml.LoadOfferings</td>
    078 * <td>{@link Boolean}</td>
    079 * <td>If true, load offerings (and their stucture, i.e., courses,
    080 * configurations, subparts and sections)</td>
    081 * </tr>
    082 * <tr>
    083 * <td>Xml.LoadStudents</td>
    084 * <td>{@link Boolean}</td>
    085 * <td>If true, load students (and their requests)</td>
    086 * </tr>
    087 * <tr>
    088 * <td>Xml.StudentFilter</td>
    089 * <td>{@link StudentFilter}</td>
    090 * <td>If provided, students are filtered by the given student filter</td>
    091 * </tr>
    092 * </table>
    093 *
    094 * <br>
    095 * <br>
    096 * Usage:<br>
    097 * <code>
    098 * StudentSectioningModel model = new StudentSectioningModel(cfg);<br>
    099 * new StudentSectioningXMLLoader(model).load();<br>
    100 * </code>
    101 *
    102 * @version StudentSct 1.2 (Student Sectioning)<br>
    103 *          Copyright (C) 2007 - 2010 Tomáš Müller<br>
    104 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    105 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    106 * <br>
    107 *          This library is free software; you can redistribute it and/or modify
    108 *          it under the terms of the GNU Lesser General Public License as
    109 *          published by the Free Software Foundation; either version 3 of the
    110 *          License, or (at your option) any later version. <br>
    111 * <br>
    112 *          This library is distributed in the hope that it will be useful, but
    113 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    114 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    115 *          Lesser General Public License for more details. <br>
    116 * <br>
    117 *          You should have received a copy of the GNU Lesser General Public
    118 *          License along with this library; if not see
    119 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    120 */
    121
    122public class StudentSectioningXMLLoader extends StudentSectioningLoader {
    123    private static org.apache.log4j.Logger sLogger = org.apache.log4j.Logger
    124            .getLogger(StudentSectioningXMLLoader.class);
    125
    126    private File iInputFile;
    127    private File iTimetableFile = null;
    128    private boolean iLoadBest = false;
    129    private boolean iLoadInitial = false;
    130    private boolean iLoadCurrent = false;
    131    private boolean iLoadOfferings = true;
    132    private boolean iLoadStudents = true;
    133    private StudentFilter iStudentFilter = null;
    134
    135    /**
    136     * Constructor
    137     *
    138     * @param model
    139     *            student sectioning model
    140     */
    141    public StudentSectioningXMLLoader(StudentSectioningModel model) {
    142        super(model);
    143        iInputFile = new File(getModel().getProperties().getProperty("General.Input",
    144                "." + File.separator + "solution.xml"));
    145        if (getModel().getProperties().getProperty("General.InputTimetable") != null)
    146            iTimetableFile = new File(getModel().getProperties().getProperty("General.InputTimetable"));
    147        iLoadBest = getModel().getProperties().getPropertyBoolean("Xml.LoadBest", true);
    148        iLoadInitial = getModel().getProperties().getPropertyBoolean("Xml.LoadInitial", true);
    149        iLoadCurrent = getModel().getProperties().getPropertyBoolean("Xml.LoadCurrent", true);
    150        iLoadOfferings = getModel().getProperties().getPropertyBoolean("Xml.LoadOfferings", true);
    151        iLoadStudents = getModel().getProperties().getPropertyBoolean("Xml.LoadStudents", true);
    152        if (getModel().getProperties().getProperty("Xml.StudentFilter") != null) {
    153            try {
    154                iStudentFilter = (StudentFilter) Class.forName(
    155                        getModel().getProperties().getProperty("Xml.StudentFilter")).getConstructor(new Class[] {})
    156                        .newInstance(new Object[] {});
    157            } catch (Exception e) {
    158                sLogger.error("Unable to create student filter, reason: " + e.getMessage(), e);
    159            }
    160        }
    161    }
    162
    163    /** Set input file (e.g., if it is not set by General.Input property) */
    164    public void setInputFile(File inputFile) {
    165        iInputFile = inputFile;
    166    }
    167
    168    /** Set student filter */
    169    public void setStudentFilter(StudentFilter filter) {
    170        iStudentFilter = filter;
    171    }
    172
    173    /** Set whether to load students */
    174    public void setLoadStudents(boolean loadStudents) {
    175        iLoadStudents = loadStudents;
    176    }
    177
    178    /** Set whether to load offerings */
    179    public void setLoadOfferings(boolean loadOfferings) {
    180        iLoadOfferings = loadOfferings;
    181    }
    182
    183    /** Create BitSet from a bit string */
    184    private static BitSet createBitSet(String bitString) {
    185        BitSet ret = new BitSet(bitString.length());
    186        for (int i = 0; i < bitString.length(); i++)
    187            if (bitString.charAt(i) == '1')
    188                ret.set(i);
    189        return ret;
    190    }
    191
    192    /** Load the file */
    193    @Override
    194    public void load() throws Exception {
    195        sLogger.debug("Reading XML data from " + iInputFile);
    196
    197        Document document = (new SAXReader()).read(iInputFile);
    198        Element root = document.getRootElement();
    199
    200        load(root);
    201    }
    202
    203    /**
    204     * Load data from the given XML root
    205     * @param root document root
    206     * @throws DocumentException
    207     */
    208    protected void load(Element root) throws DocumentException {
    209        sLogger.debug("Root element: " + root.getName());
    210        if (!"sectioning".equals(root.getName())) {
    211            sLogger.error("Given XML file is not student sectioning problem.");
    212            return;
    213        }
    214
    215        if (iLoadOfferings && getModel().getDistanceConflict() != null && root.element("travel-times") != null)
    216            loadTravelTimes(root.element("travel-times"), getModel().getDistanceConflict().getDistanceMetric());
    217
    218        Map<Long, Placement> timetable = null;
    219        if (iTimetableFile != null) {
    220            sLogger.info("Reading timetable from " + iTimetableFile + " ...");
    221            Document timetableDocument = (new SAXReader()).read(iTimetableFile);
    222            Element timetableRoot = timetableDocument.getRootElement();
    223            if (!"timetable".equals(timetableRoot.getName())) {
    224                sLogger.error("Given XML file is not course timetabling problem.");
    225                return;
    226            }
    227            timetable = loadTimetable(timetableRoot);
    228        }
    229
    230        Progress.getInstance(getModel()).load(root, true);
    231        Progress.getInstance(getModel()).message(Progress.MSGLEVEL_STAGE, "Restoring from backup ...");
    232
    233        if (root.attributeValue("term") != null)
    234            getModel().getProperties().setProperty("Data.Term", root.attributeValue("term"));
    235        if (root.attributeValue("year") != null)
    236            getModel().getProperties().setProperty("Data.Year", root.attributeValue("year"));
    237        if (root.attributeValue("initiative") != null)
    238            getModel().getProperties().setProperty("Data.Initiative", root.attributeValue("initiative"));
    239
    240        Map<Long, Offering> offeringTable = new HashMap<Long, Offering>();
    241        Map<Long, Course> courseTable = new HashMap<Long, Course>();
    242
    243        if (iLoadOfferings && root.element("offerings") != null) {
    244            loadOfferings(root.element("offerings"), offeringTable, courseTable, timetable);
    245        } else {
    246            for (Offering offering : getModel().getOfferings()) {
    247                offeringTable.put(new Long(offering.getId()), offering);
    248                for (Course course : offering.getCourses()) {
    249                    courseTable.put(new Long(course.getId()), course);
    250                }
    251            }
    252        }
    253
    254        if (iLoadStudents && root.element("students") != null) {
    255            loadStudents(root.element("students"), offeringTable, courseTable);
    256        }
    257
    258        if (iLoadOfferings && root.element("constraints") != null)
    259            loadLinkedSectiond(root.element("constraints"), offeringTable);
    260
    261        sLogger.debug("Model successfully loaded.");
    262    }
    263
    264    /**
    265     * Load offerings
    266     * @param offeringsEl offerings element
    267     * @param offeringTable offering table
    268     * @param courseTable course table
    269     * @param timetable provided timetable (null if to be loaded from the given document)
    270     */
    271    protected void loadOfferings(Element offeringsEl, Map<Long, Offering> offeringTable, Map<Long, Course> courseTable, Map<Long, Placement> timetable) {
    272        HashMap<Long, Config> configTable = new HashMap<Long, Config>();
    273        HashMap<Long, Subpart> subpartTable = new HashMap<Long, Subpart>();
    274        HashMap<Long, Section> sectionTable = new HashMap<Long, Section>();
    275        for (Iterator<?> i = offeringsEl.elementIterator("offering"); i.hasNext();) {
    276            Element offeringEl = (Element) i.next();
    277            Offering offering = new Offering(
    278                    Long.parseLong(offeringEl.attributeValue("id")),
    279                    offeringEl.attributeValue("name", "O" + offeringEl.attributeValue("id")));
    280            offeringTable.put(new Long(offering.getId()), offering);
    281            getModel().addOffering(offering);
    282
    283            for (Iterator<?> j = offeringEl.elementIterator("course"); j.hasNext();) {
    284                Element courseEl = (Element) j.next();
    285                Course course = loadCourse(courseEl, offering);
    286                courseTable.put(new Long(course.getId()), course);
    287            }
    288
    289            for (Iterator<?> j = offeringEl.elementIterator("config"); j.hasNext();) {
    290                Element configEl = (Element) j.next();
    291                Config config = loadConfig(configEl, offering, subpartTable, sectionTable, timetable);
    292                configTable.put(config.getId(), config);
    293            }
    294
    295            for (Iterator<?> j = offeringEl.elementIterator("reservation"); j.hasNext(); ) {
    296                Element reservationEl = (Element)j.next();
    297                loadReservation(reservationEl, offering, configTable, sectionTable);
    298            }
    299        }
    300    }
    301
    302    /**
    303     * Load course
    304     * @param courseEl course element
    305     * @param offering parent offering
    306     * @return loaded course
    307     */
    308    protected Course loadCourse(Element courseEl, Offering offering) {
    309        Course course = new Course(
    310                Long.parseLong(courseEl.attributeValue("id")),
    311                courseEl.attributeValue("subjectArea", ""),
    312                courseEl.attributeValue("courseNbr", "C" + courseEl.attributeValue("id")),
    313                offering, Integer.parseInt(courseEl.attributeValue("limit", "-1")),
    314                Integer.parseInt(courseEl.attributeValue("projected", "0")));
    315        return course;
    316    }
    317
    318    /**
    319     * Load config
    320     * @param configEl config element
    321     * @param offering parent offering
    322     * @param subpartTable subpart table (of the offering)
    323     * @param sectionTable section table (of the offering)
    324     * @param timetable provided timetable
    325     * @return loaded config
    326     */
    327    protected Config loadConfig(Element configEl, Offering offering, Map<Long, Subpart> subpartTable, Map<Long, Section> sectionTable, Map<Long, Placement> timetable) {
    328        Config config = new Config
    329                (Long.parseLong(configEl.attributeValue("id")),
    330                Integer.parseInt(configEl.attributeValue("limit", "-1")),
    331                configEl.attributeValue("name", "G" + configEl.attributeValue("id")),
    332                offering);
    333        for (Iterator<?> k = configEl.elementIterator("subpart"); k.hasNext();) {
    334            Element subpartEl = (Element) k.next();
    335            Subpart subpart = loadSubpart(subpartEl, config, subpartTable, sectionTable, timetable);
    336            subpartTable.put(new Long(subpart.getId()), subpart);
    337        }
    338        return config;
    339    }
    340
    341    /**
    342     * Load subpart
    343     * @param subpartEl supart element
    344     * @param config parent config
    345     * @param subpartTable subpart table (of the offering)
    346     * @param sectionTable section table (of the offering)
    347     * @param timetable provided timetable
    348     * @return loaded subpart
    349     */
    350    protected Subpart loadSubpart(Element subpartEl, Config config, Map<Long, Subpart> subpartTable, Map<Long, Section> sectionTable, Map<Long, Placement> timetable) {
    351        Subpart parentSubpart = null;
    352        if (subpartEl.attributeValue("parent") != null)
    353            parentSubpart = subpartTable.get(Long.valueOf(subpartEl.attributeValue("parent")));
    354        Subpart subpart = new Subpart(
    355                Long.parseLong(subpartEl.attributeValue("id")),
    356                subpartEl.attributeValue("itype"),
    357                subpartEl.attributeValue("name", "P" + subpartEl.attributeValue("id")),
    358                config,
    359                parentSubpart);
    360        subpart.setAllowOverlap("true".equals(subpartEl.attributeValue("allowOverlap", "false")));
    361
    362
    363        for (Iterator<?> l = subpartEl.elementIterator("section"); l.hasNext();) {
    364            Element sectionEl = (Element) l.next();
    365            Section section = loadSection(sectionEl, subpart, sectionTable, timetable);
    366            sectionTable.put(new Long(section.getId()), section);
    367        }
    368
    369        return subpart;
    370    }
    371
    372    /**
    373     * Load section
    374     * @param sectionEl section element
    375     * @param subpart parent subpart
    376     * @param sectionTable section table (of the offering)
    377     * @param timetable provided timetable
    378     * @return loaded section
    379     */
    380    protected Section loadSection(Element sectionEl, Subpart subpart, Map<Long, Section> sectionTable, Map<Long, Placement> timetable) {
    381        Section parentSection = null;
    382        if (sectionEl.attributeValue("parent") != null)
    383            parentSection = sectionTable.get(Long.valueOf(sectionEl.attributeValue("parent")));
    384        Placement placement = null;
    385        if (timetable != null) {
    386            placement = timetable.get(Long.parseLong(sectionEl.attributeValue("id")));
    387        } else {
    388            TimeLocation time = null;
    389            Element timeEl = sectionEl.element("time");
    390            if (timeEl != null) {
    391                time = new TimeLocation(
    392                        Integer.parseInt(timeEl.attributeValue("days"), 2),
    393                        Integer.parseInt(timeEl.attributeValue("start")),
    394                        Integer.parseInt(timeEl.attributeValue("length")), 0, 0,
    395                        timeEl.attributeValue("datePattern") == null ? null : Long.valueOf(timeEl.attributeValue("datePattern")),
    396                        timeEl.attributeValue("datePatternName", ""),
    397                        createBitSet(timeEl.attributeValue("dates")),
    398                        Integer.parseInt(timeEl.attributeValue("breakTime", "0")));
    399                if (timeEl.attributeValue("pattern") != null)
    400                    time.setTimePatternId(Long.valueOf(timeEl.attributeValue("pattern")));
    401            }
    402            List<RoomLocation> rooms = new ArrayList<RoomLocation>();
    403            for (Iterator<?> m = sectionEl.elementIterator("room"); m.hasNext();) {
    404                Element roomEl = (Element) m.next();
    405                Double posX = null, posY = null;
    406                if (roomEl.attributeValue("location") != null) {
    407                    String loc = roomEl.attributeValue("location");
    408                    posX = Double.valueOf(loc.substring(0, loc.indexOf(',')));
    409                    posY = Double.valueOf(loc.substring(loc.indexOf(',') + 1));
    410                }
    411                RoomLocation room = new RoomLocation(
    412                        Long.valueOf(roomEl.attributeValue("id")),
    413                        roomEl.attributeValue("name", "R" + roomEl.attributeValue("id")),
    414                        roomEl.attributeValue("building") == null ? null : Long.valueOf(roomEl.attributeValue("building")),
    415                        0, Integer.parseInt(roomEl.attributeValue("capacity")),
    416                        posX, posY, "true".equals(roomEl.attributeValue("ignoreTooFar")), null);
    417                rooms.add(room);
    418            }
    419            placement = (time == null ? null : new Placement(null, time, rooms));
    420        }
    421
    422        Section section = new Section(
    423                Long.parseLong(sectionEl.attributeValue("id")),
    424                Integer.parseInt(sectionEl.attributeValue("limit")),
    425                sectionEl.attributeValue("name", "S" + sectionEl.attributeValue("id")),
    426                subpart, placement, sectionEl.attributeValue("instructorIds"),
    427                sectionEl.attributeValue("instructorNames"), parentSection);
    428
    429        section.setSpaceHeld(Double.parseDouble(sectionEl.attributeValue("hold", "0.0")));
    430        section.setSpaceExpected(Double.parseDouble(sectionEl.attributeValue("expect", "0.0")));
    431        for (Iterator<?> m = sectionEl.elementIterator("cname"); m.hasNext(); ) {
    432            Element cNameEl = (Element)m.next();
    433            section.setName(Long.parseLong(cNameEl.attributeValue("id")), cNameEl.getText());
    434        }
    435        Element ignoreEl = sectionEl.element("no-conflicts");
    436        if (ignoreEl != null) {
    437            for (Iterator<?> m = ignoreEl.elementIterator("section"); m.hasNext(); )
    438                section.addIgnoreConflictWith(Long.parseLong(((Element)m.next()).attributeValue("id")));
    439        }
    440
    441        return section;
    442    }
    443
    444    /**
    445     * Load reservation
    446     * @param reservationEl reservation element
    447     * @param offering parent offering
    448     * @param configTable config table (of the offering)
    449     * @param sectionTable section table (of the offering)
    450     * @return loaded reservation
    451     */
    452    protected Reservation loadReservation(Element reservationEl, Offering offering, HashMap<Long, Config> configTable, HashMap<Long, Section> sectionTable) {
    453        Reservation r = null;
    454        if ("individual".equals(reservationEl.attributeValue("type"))) {
    455            Set<Long> studentIds = new HashSet<Long>();
    456            for (Iterator<?> k = reservationEl.elementIterator("student"); k.hasNext(); ) {
    457                Element studentEl = (Element)k.next();
    458                studentIds.add(Long.parseLong(studentEl.attributeValue("id")));
    459            }
    460            r = new IndividualReservation(Long.valueOf(reservationEl.attributeValue("id")), offering, studentIds);
    461        } else if ("group".equals(reservationEl.attributeValue("type"))) {
    462            Set<Long> studentIds = new HashSet<Long>();
    463            for (Iterator<?> k = reservationEl.elementIterator("student"); k.hasNext(); ) {
    464                Element studentEl = (Element)k.next();
    465                studentIds.add(Long.parseLong(studentEl.attributeValue("id")));
    466            }
    467            r = new GroupReservation(Long.valueOf(reservationEl.attributeValue("id")),
    468                    Double.parseDouble(reservationEl.attributeValue("limit", "-1")),
    469                    offering, studentIds);
    470        } else if ("curriculum".equals(reservationEl.attributeValue("type"))) {
    471            List<String> classifications = new ArrayList<String>();
    472            for (Iterator<?> k = reservationEl.elementIterator("classification"); k.hasNext(); ) {
    473                Element clasfEl = (Element)k.next();
    474                classifications.add(clasfEl.attributeValue("code"));
    475            }
    476            List<String> majors = new ArrayList<String>();
    477            for (Iterator<?> k = reservationEl.elementIterator("major"); k.hasNext(); ) {
    478                Element majorEl = (Element)k.next();
    479                majors.add(majorEl.attributeValue("code"));
    480            }
    481            r = new CurriculumReservation(Long.valueOf(reservationEl.attributeValue("id")),
    482                    Double.parseDouble(reservationEl.attributeValue("limit", "-1")),
    483                    offering,
    484                    reservationEl.attributeValue("area"),
    485                    classifications, majors);
    486        } else if ("course".equals(reservationEl.attributeValue("type"))) {
    487            long courseId = Long.parseLong(reservationEl.attributeValue("course"));
    488            for (Course course: offering.getCourses()) {
    489                if (course.getId() == courseId)
    490                    r = new CourseReservation(Long.valueOf(reservationEl.attributeValue("id")), course);
    491            }
    492        } else if ("dummy".equals(reservationEl.attributeValue("type"))) {
    493            r = new DummyReservation(offering);
    494        }
    495        if (r == null) {
    496            sLogger.error("Unknown reservation type "+ reservationEl.attributeValue("type"));
    497            return null;
    498        }
    499        r.setExpired("true".equals(reservationEl.attributeValue("expired", "false")));
    500        for (Iterator<?> k = reservationEl.elementIterator("config"); k.hasNext(); ) {
    501            Element configEl = (Element)k.next();
    502            r.addConfig(configTable.get(Long.parseLong(configEl.attributeValue("id"))));
    503        }
    504        for (Iterator<?> k = reservationEl.elementIterator("section"); k.hasNext(); ) {
    505            Element sectionEl = (Element)k.next();
    506            r.addSection(sectionTable.get(Long.parseLong(sectionEl.attributeValue("id"))));
    507        }
    508        return r;
    509    }
    510
    511    /**
    512     * Load given timetable
    513     * @param timetableRoot document root in the course timetabling XML format
    514     * @return loaded timetable (map class id: assigned placement)
    515     */
    516    protected Map<Long, Placement> loadTimetable(Element timetableRoot) {
    517        Map<Long, Placement> timetable = new HashMap<Long, Placement>();
    518        HashMap<Long, RoomLocation> rooms = new HashMap<Long, RoomLocation>();
    519        for (Iterator<?> i = timetableRoot.element("rooms").elementIterator("room"); i.hasNext();) {
    520            Element roomEl = (Element)i.next();
    521            Long roomId = Long.valueOf(roomEl.attributeValue("id"));
    522            Double posX = null, posY = null;
    523            if (roomEl.attributeValue("location") != null) {
    524                String loc = roomEl.attributeValue("location");
    525                posX = Double.valueOf(loc.substring(0, loc.indexOf(',')));
    526                posY = Double.valueOf(loc.substring(loc.indexOf(',') + 1));
    527            }
    528            RoomLocation room = new RoomLocation(
    529                    Long.valueOf(roomEl.attributeValue("id")),
    530                    roomEl.attributeValue("name", "R" + roomEl.attributeValue("id")),
    531                    roomEl.attributeValue("building") == null ? null : Long.valueOf(roomEl.attributeValue("building")),
    532                    0, Integer.parseInt(roomEl.attributeValue("capacity")),
    533                    posX, posY, "true".equals(roomEl.attributeValue("ignoreTooFar")), null);
    534            rooms.put(roomId, room);
    535        }
    536        for (Iterator<?> i = timetableRoot.element("classes").elementIterator("class"); i.hasNext();) {
    537            Element classEl = (Element)i.next();
    538            Long classId = Long.valueOf(classEl.attributeValue("id"));
    539            TimeLocation time = null;
    540            Element timeEl = null;
    541            for (Iterator<?> j = classEl.elementIterator("time"); j.hasNext(); ) {
    542                Element e = (Element)j.next();
    543                if ("true".equals(e.attributeValue("solution", "false"))) { timeEl = e; break; }
    544            }
    545            if (timeEl != null) {
    546                time = new TimeLocation(
    547                        Integer.parseInt(timeEl.attributeValue("days"), 2),
    548                        Integer.parseInt(timeEl.attributeValue("start")),
    549                        Integer.parseInt(timeEl.attributeValue("length")), 0, 0,
    550                        classEl.attributeValue("datePattern") == null ? null : Long.valueOf(classEl.attributeValue("datePattern")),
    551                        classEl.attributeValue("datePatternName", ""), createBitSet(classEl.attributeValue("dates")),
    552                        Integer.parseInt(timeEl.attributeValue("breakTime", "0")));
    553                if (timeEl.attributeValue("pattern") != null)
    554                    time.setTimePatternId(Long.valueOf(timeEl.attributeValue("pattern")));
    555            }
    556            List<RoomLocation> room = new ArrayList<RoomLocation>();
    557            for (Iterator<?> j = classEl.elementIterator("room"); j.hasNext();) {
    558                Element roomEl = (Element) j.next();
    559                if (!"true".equals(roomEl.attributeValue("solution", "false"))) continue;
    560                room.add(rooms.get(Long.valueOf(roomEl.attributeValue("id"))));
    561            }
    562            Placement placement = (time == null ? null : new Placement(null, time, room));
    563            if (placement != null)
    564                timetable.put(classId, placement);
    565        }
    566        return timetable;
    567    }
    568
    569    /**
    570     * Load travel times
    571     * @param travelTimesEl travel-time element
    572     * @param metric distance metric to be populated
    573     */
    574    protected void loadTravelTimes(Element travelTimesEl, DistanceMetric metric) {
    575        for (Iterator<?> i = travelTimesEl.elementIterator("travel-time"); i.hasNext();) {
    576            Element travelTimeEl = (Element)i.next();
    577            metric.addTravelTime(
    578                    Long.valueOf(travelTimeEl.attributeValue("id1")),
    579                    Long.valueOf(travelTimeEl.attributeValue("id2")),
    580                    Integer.valueOf(travelTimeEl.attributeValue("minutes")));
    581        }
    582    }
    583
    584    /**
    585     * Load linked sections
    586     * @param constraintsEl constraints element
    587     * @param offeringTable offering table
    588     */
    589    protected void loadLinkedSectiond(Element constraintsEl, Map<Long, Offering> offeringTable) {
    590        for (Iterator<?> i = constraintsEl.elementIterator("linked-sections"); i.hasNext();) {
    591            Element linkedEl = (Element) i.next();
    592            List<Section> sections = new ArrayList<Section>();
    593            for (Iterator<?> j = linkedEl.elementIterator("section"); j.hasNext();) {
    594                Element sectionEl = (Element) j.next();
    595                Offering offering = offeringTable.get(Long.valueOf(sectionEl.attributeValue("offering")));
    596                sections.add(offering.getSection(Long.valueOf(sectionEl.attributeValue("id"))));
    597            }
    598            getModel().addLinkedSections(sections);
    599        }
    600    }
    601
    602    /**
    603     * Load students
    604     * @param studentsEl students element
    605     * @param offeringTable offering table
    606     * @param courseTable course table
    607     */
    608    protected void loadStudents(Element studentsEl, Map<Long, Offering> offeringTable, Map<Long, Course> courseTable) {
    609        List<Enrollment> bestEnrollments = new ArrayList<Enrollment>();
    610        List<Enrollment> currentEnrollments = new ArrayList<Enrollment>();
    611        for (Iterator<?> i = studentsEl.elementIterator("student"); i.hasNext();) {
    612            Element studentEl = (Element) i.next();
    613            Student student = loadStudent(studentEl);
    614            if (iStudentFilter != null && !iStudentFilter.accept(student))
    615                continue;
    616            for (Iterator<?> j = studentEl.elementIterator(); j.hasNext();) {
    617                Element requestEl = (Element) j.next();
    618                Request request = loadRequest(requestEl, student, offeringTable, courseTable);
    619                if (request == null) continue;
    620
    621                Element initialEl = requestEl.element("initial");
    622                if (iLoadInitial && initialEl != null) {
    623                    Enrollment enrollment = loadEnrollment(initialEl, request);
    624                    if (enrollment != null)
    625                        request.setInitialAssignment(enrollment);
    626                }
    627                Element currentEl = requestEl.element("current");
    628                if (iLoadCurrent && currentEl != null) {
    629                    Enrollment enrollment = loadEnrollment(currentEl, request);
    630                    if (enrollment != null)
    631                        currentEnrollments.add(enrollment);
    632                }
    633                Element bestEl = requestEl.element("best");
    634                if (iLoadBest && bestEl != null) {
    635                    Enrollment enrollment = loadEnrollment(bestEl, request);
    636                    if (enrollment != null)
    637                        bestEnrollments.add(enrollment);
    638                }
    639            }
    640            getModel().addStudent(student);
    641        }
    642
    643        if (!bestEnrollments.isEmpty()) {
    644            // Enrollments with a reservation must go first
    645            for (Enrollment enrollment : bestEnrollments) {
    646                if (enrollment.getReservation() == null) continue;
    647                Map<Constraint<Request, Enrollment>, Set<Enrollment>> conflicts = getModel().conflictConstraints(enrollment);
    648                if (conflicts.isEmpty())
    649                    enrollment.variable().assign(0, enrollment);
    650                else
    651                    sLogger.warn("Enrollment " + enrollment + " conflicts with " + conflicts);
    652            }
    653            for (Enrollment enrollment : bestEnrollments) {
    654                if (enrollment.getReservation() != null) continue;
    655                Map<Constraint<Request, Enrollment>, Set<Enrollment>> conflicts = getModel().conflictConstraints(enrollment);
    656                if (conflicts.isEmpty())
    657                    enrollment.variable().assign(0, enrollment);
    658                else
    659                    sLogger.warn("Enrollment " + enrollment + " conflicts with " + conflicts);
    660            }
    661            getModel().saveBest();
    662        }
    663
    664        if (!currentEnrollments.isEmpty()) {
    665            for (Request request : getModel().variables()) {
    666                if (request.getAssignment() != null)
    667                    request.unassign(0);
    668            }
    669            // Enrollments with a reservation must go first
    670            for (Enrollment enrollment : currentEnrollments) {
    671                if (enrollment.getReservation() == null) continue;
    672                Map<Constraint<Request, Enrollment>, Set<Enrollment>> conflicts = getModel().conflictConstraints(enrollment);
    673                if (conflicts.isEmpty())
    674                    enrollment.variable().assign(0, enrollment);
    675                else
    676                    sLogger.warn("Enrollment " + enrollment + " conflicts with " + conflicts);
    677            }
    678            for (Enrollment enrollment : currentEnrollments) {
    679                if (enrollment.getReservation() != null) continue;
    680                Map<Constraint<Request, Enrollment>, Set<Enrollment>> conflicts = getModel().conflictConstraints(enrollment);
    681                if (conflicts.isEmpty())
    682                    enrollment.variable().assign(0, enrollment);
    683                else
    684                    sLogger.warn("Enrollment " + enrollment + " conflicts with " + conflicts);
    685            }
    686        }
    687    }
    688
    689    /**
    690     * Load student
    691     * @param studentEl student element
    692     * @return loaded student
    693     */
    694    protected Student loadStudent(Element studentEl) {
    695        Student student = new Student(Long.parseLong(studentEl.attributeValue("id")), "true".equals(studentEl.attributeValue("dummy")));
    696        student.setExternalId(studentEl.attributeValue("externalId"));
    697        student.setName(studentEl.attributeValue("name"));
    698        student.setStatus(studentEl.attributeValue("status"));
    699        for (Iterator<?> j = studentEl.elementIterator(); j.hasNext();) {
    700            Element requestEl = (Element) j.next();
    701            if ("classification".equals(requestEl.getName())) {
    702                student.getAcademicAreaClasiffications().add(
    703                        new AcademicAreaCode(requestEl.attributeValue("area"), requestEl.attributeValue("code")));
    704            } else if ("major".equals(requestEl.getName())) {
    705                student.getMajors().add(
    706                        new AcademicAreaCode(requestEl.attributeValue("area"), requestEl.attributeValue("code")));
    707            } else if ("minor".equals(requestEl.getName())) {
    708                student.getMinors().add(
    709                        new AcademicAreaCode(requestEl.attributeValue("area"), requestEl.attributeValue("code")));
    710            }
    711        }
    712        return student;
    713    }
    714
    715    /**
    716     * Load request
    717     * @param requestEl request element
    718     * @param student parent student
    719     * @param offeringTable offering table
    720     * @param courseTable course table
    721     * @return loaded request
    722     */
    723    protected Request loadRequest(Element requestEl, Student student, Map<Long, Offering> offeringTable, Map<Long, Course> courseTable) {
    724        if ("freeTime".equals(requestEl.getName())) {
    725            return loadFreeTime(requestEl, student);
    726        } else if ("course".equals(requestEl.getName())) {
    727            return loadCourseRequest(requestEl, student, offeringTable, courseTable);
    728        } else {
    729            return null;
    730        }
    731    }
    732
    733    /**
    734     * Load free time request
    735     * @param requestEl request element
    736     * @param student parent student
    737     * @return loaded free time request
    738     */
    739    public FreeTimeRequest loadFreeTime(Element requestEl, Student student) {
    740        TimeLocation time = new TimeLocation(Integer.parseInt(requestEl.attributeValue("days"), 2),
    741                Integer.parseInt(requestEl.attributeValue("start")), Integer.parseInt(requestEl
    742                        .attributeValue("length")), 0, 0,
    743                requestEl.attributeValue("datePattern") == null ? null : Long.valueOf(requestEl
    744                        .attributeValue("datePattern")), "", createBitSet(requestEl
    745                        .attributeValue("dates")), 0);
    746        FreeTimeRequest request = new FreeTimeRequest(Long.parseLong(requestEl.attributeValue("id")),
    747                Integer.parseInt(requestEl.attributeValue("priority")), "true".equals(requestEl
    748                        .attributeValue("alternative")), student, time);
    749        if (requestEl.attributeValue("weight") != null)
    750            request.setWeight(Double.parseDouble(requestEl.attributeValue("weight")));
    751        return request;
    752    }
    753
    754    /**
    755     * Load course request
    756     * @param requestEl request element
    757     * @param student parent student
    758     * @param offeringTable offering table
    759     * @param courseTable course table
    760     * @return loaded course request
    761     */
    762    public CourseRequest loadCourseRequest(Element requestEl, Student student, Map<Long, Offering> offeringTable, Map<Long, Course> courseTable) {
    763        List<Course> courses = new ArrayList<Course>();
    764        courses.add(courseTable.get(Long.valueOf(requestEl.attributeValue("course"))));
    765        for (Iterator<?> k = requestEl.elementIterator("alternative"); k.hasNext();)
    766            courses.add(courseTable.get(Long.valueOf(((Element) k.next()).attributeValue("course"))));
    767        Long timeStamp = null;
    768        if (requestEl.attributeValue("timeStamp") != null)
    769            timeStamp = Long.valueOf(requestEl.attributeValue("timeStamp"));
    770        CourseRequest courseRequest = new CourseRequest(
    771                Long.parseLong(requestEl.attributeValue("id")),
    772                Integer.parseInt(requestEl.attributeValue("priority")),
    773                "true".equals(requestEl.attributeValue("alternative")),
    774                student, courses,
    775                "true".equals(requestEl.attributeValue("waitlist", "false")), timeStamp);
    776        if (requestEl.attributeValue("weight") != null)
    777            courseRequest.setWeight(Double.parseDouble(requestEl.attributeValue("weight")));
    778        for (Iterator<?> k = requestEl.elementIterator("waitlisted"); k.hasNext();) {
    779            Element choiceEl = (Element) k.next();
    780            courseRequest.getWaitlistedChoices().add(
    781                    new Choice(offeringTable.get(Long.valueOf(choiceEl.attributeValue("offering"))), choiceEl.getText()));
    782        }
    783        for (Iterator<?> k = requestEl.elementIterator("selected"); k.hasNext();) {
    784            Element choiceEl = (Element) k.next();
    785            courseRequest.getSelectedChoices().add(
    786                    new Choice(offeringTable.get(Long.valueOf(choiceEl.attributeValue("offering"))), choiceEl.getText()));
    787        }
    788        return courseRequest;
    789    }
    790
    791    /**
    792     * Load enrollment
    793     * @param enrollmentEl enrollment element (current, best, or initial)
    794     * @param request parent request
    795     * @return loaded enrollment
    796     */
    797    protected Enrollment loadEnrollment(Element enrollmentEl, Request request) {
    798        if (request instanceof CourseRequest) {
    799            CourseRequest courseRequest = (CourseRequest) request;
    800            HashSet<Section> sections = new HashSet<Section>();
    801            for (Iterator<?> k = enrollmentEl.elementIterator("section"); k.hasNext();) {
    802                Element sectionEl = (Element) k.next();
    803                Section section = courseRequest.getSection(Long.parseLong(sectionEl.attributeValue("id")));
    804                sections.add(section);
    805            }
    806            Reservation reservation = null;
    807            if (enrollmentEl.attributeValue("reservation", null) != null) {
    808                long reservationId = Long.valueOf(enrollmentEl.attributeValue("reservation"));
    809                for (Course course: courseRequest.getCourses())
    810                    for (Reservation r: course.getOffering().getReservations())
    811                        if (r.getId() == reservationId) { reservation = r; break; }
    812            }
    813            if (!sections.isEmpty())
    814                return courseRequest.createEnrollment(sections, reservation);
    815        } else if (request instanceof FreeTimeRequest) {
    816            return ((FreeTimeRequest)request).createEnrollment();
    817        }
    818        return null;
    819    }
    820
    821}