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