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