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