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