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