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