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