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