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