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