001 package net.sf.cpsolver.studentsct.model; 002 003 import java.text.DecimalFormat; 004 import java.util.ArrayList; 005 import java.util.Collection; 006 import java.util.Collections; 007 import java.util.HashMap; 008 import java.util.HashSet; 009 import java.util.List; 010 import java.util.Map; 011 import java.util.Set; 012 import java.util.TreeSet; 013 014 import net.sf.cpsolver.coursett.model.TimeLocation; 015 import net.sf.cpsolver.ifs.util.ToolBox; 016 import net.sf.cpsolver.studentsct.StudentSectioningModel; 017 import net.sf.cpsolver.studentsct.constraint.ConfigLimit; 018 import net.sf.cpsolver.studentsct.constraint.CourseLimit; 019 import net.sf.cpsolver.studentsct.constraint.SectionLimit; 020 import net.sf.cpsolver.studentsct.reservation.Reservation; 021 022 /** 023 * Representation of a request of a student for one or more course. A student 024 * requests one of the given courses, preferably the first one. <br> 025 * <br> 026 * 027 * @version StudentSct 1.2 (Student Sectioning)<br> 028 * Copyright (C) 2007 - 2010 Tomas Muller<br> 029 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 030 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 031 * <br> 032 * This library is free software; you can redistribute it and/or modify 033 * it under the terms of the GNU Lesser General Public License as 034 * published by the Free Software Foundation; either version 3 of the 035 * License, or (at your option) any later version. <br> 036 * <br> 037 * This library is distributed in the hope that it will be useful, but 038 * WITHOUT ANY WARRANTY; without even the implied warranty of 039 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 040 * Lesser General Public License for more details. <br> 041 * <br> 042 * You should have received a copy of the GNU Lesser General Public 043 * License along with this library; if not see 044 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 045 */ 046 public class CourseRequest extends Request { 047 private static DecimalFormat sDF = new DecimalFormat("0.000"); 048 private List<Course> iCourses = null; 049 private Set<Choice> iWaitlistedChoices = new HashSet<Choice>(); 050 private Set<Choice> iSelectedChoices = new HashSet<Choice>(); 051 private boolean iWaitlist = false; 052 private Long iTimeStamp = null; 053 private Double iCachedMinPenalty = null, iCachedMaxPenalty = null; 054 public static boolean sSameTimePrecise = false; 055 056 /** 057 * Constructor 058 * 059 * @param id 060 * request unique id 061 * @param priority 062 * request priority 063 * @param alternative 064 * true if the request is alternative (alternative request can be 065 * assigned instead of a non-alternative course requests, if it 066 * is left unassigned) 067 * @param student 068 * appropriate student 069 * @param courses 070 * list of requested courses (in the correct order -- first is 071 * the requested course, second is the first alternative, etc.) 072 * @param waitlist 073 * time stamp of the request if the student can be put on a wait-list (no alternative 074 * course request will be given instead) 075 */ 076 public CourseRequest(long id, int priority, boolean alternative, Student student, java.util.List<Course> courses, 077 boolean waitlist, Long timeStamp) { 078 super(id, priority, alternative, student); 079 iCourses = new ArrayList<Course>(courses); 080 for (Course course: iCourses) 081 course.getRequests().add(this); 082 iWaitlist = waitlist; 083 iTimeStamp = timeStamp; 084 } 085 086 /** 087 * List of requested courses (in the correct order -- first is the requested 088 * course, second is the first alternative, etc.) 089 */ 090 public List<Course> getCourses() { 091 return iCourses; 092 } 093 094 /** 095 * Create enrollment for the given list of sections. The list of sections 096 * needs to be correct, i.e., a section for each subpart of a configuration 097 * of one of the requested courses. 098 */ 099 public Enrollment createEnrollment(Set<? extends Assignment> sections, Reservation reservation) { 100 if (sections == null || sections.isEmpty()) 101 return null; 102 Config config = ((Section) sections.iterator().next()).getSubpart().getConfig(); 103 Course course = null; 104 for (Course c: iCourses) { 105 if (c.getOffering().getConfigs().contains(config)) { 106 course = c; 107 break; 108 } 109 } 110 return new Enrollment(this, iCourses.indexOf(course), course, config, sections, reservation); 111 } 112 113 /** 114 * Create enrollment for the given list of sections. The list of sections 115 * needs to be correct, i.e., a section for each subpart of a configuration 116 * of one of the requested courses. 117 */ 118 public Enrollment createEnrollment(Set<? extends Assignment> sections) { 119 Enrollment ret = createEnrollment(sections, null); 120 ret.guessReservation(true); 121 return ret; 122 123 } 124 125 /** 126 * Return all possible enrollments. 127 */ 128 @Override 129 public List<Enrollment> computeEnrollments() { 130 List<Enrollment> ret = new ArrayList<Enrollment>(); 131 int idx = 0; 132 for (Course course : iCourses) { 133 for (Config config : course.getOffering().getConfigs()) { 134 computeEnrollments(ret, idx, 0, course, config, new HashSet<Section>(), 0, false, false, 135 false, false, -1); 136 } 137 idx++; 138 } 139 return ret; 140 } 141 142 /** 143 * Return a subset of all enrollments -- randomly select only up to 144 * limitEachConfig enrollments of each config. 145 */ 146 public List<Enrollment> computeRandomEnrollments(int limitEachConfig) { 147 List<Enrollment> ret = new ArrayList<Enrollment>(); 148 int idx = 0; 149 for (Course course : iCourses) { 150 for (Config config : course.getOffering().getConfigs()) { 151 computeEnrollments(ret, idx, 0, course, config, new HashSet<Section>(), 0, false, false, 152 false, true, (limitEachConfig <= 0 ? limitEachConfig : ret.size() + limitEachConfig)); 153 } 154 idx++; 155 } 156 return ret; 157 } 158 159 /** 160 * Return true if the both sets of sections contain sections of the same 161 * subparts, and each pair of sections of the same subpart is offered at the 162 * same time. 163 */ 164 private boolean sameTimes(Set<Section> sections1, Set<Section> sections2) { 165 for (Section s1 : sections1) { 166 Section s2 = null; 167 for (Section s : sections2) { 168 if (s.getSubpart().equals(s1.getSubpart())) { 169 s2 = s; 170 break; 171 } 172 } 173 if (s2 == null) 174 return false; 175 if (!ToolBox.equals(s1.getTime(), s2.getTime())) 176 return false; 177 } 178 return true; 179 } 180 181 /** 182 * Recursive computation of enrollments 183 * 184 * @param enrollments 185 * list of enrollments to be returned 186 * @param priority 187 * zero for the course, one for the first alternative, two for the second alternative 188 * @param penalty 189 * penalty of the selected sections 190 * @param course 191 * selected course 192 * @param config 193 * selected configuration 194 * @param sections 195 * sections selected so far 196 * @param idx 197 * index of the subparts (a section of 0..idx-1 subparts has been 198 * already selected) 199 * @param availableOnly 200 * only use available sections 201 * @param skipSameTime 202 * for each possible times, pick only one section 203 * @param selectedOnly 204 * select only sections that are selected ( 205 * {@link CourseRequest#isSelected(Section)} is true) 206 * @param random 207 * pick sections in a random order (useful when limit is used) 208 * @param limit 209 * when above zero, limit the number of selected enrollments to 210 * this limit 211 * @param reservations 212 * list of applicable reservations 213 */ 214 private void computeEnrollments(Collection<Enrollment> enrollments, int priority, double penalty, Course course, Config config, 215 HashSet<Section> sections, int idx, boolean availableOnly, boolean skipSameTime, boolean selectedOnly, 216 boolean random, int limit) { 217 if (limit > 0 && enrollments.size() >= limit) 218 return; 219 if (idx == 0) { // run only once for each configuration 220 boolean canOverLimit = false; 221 if (availableOnly && (getModel() == null || ((StudentSectioningModel)getModel()).getReservationCanAssignOverTheLimit())) { 222 for (Reservation r: getReservations(course)) { 223 if (!r.canAssignOverLimit()) continue; 224 if (!r.getConfigs().isEmpty() && !r.getConfigs().contains(config)) continue; 225 if (r.getReservedAvailableSpace(this) < getWeight()) continue; 226 canOverLimit = true; break; 227 } 228 } 229 if (!canOverLimit) { 230 if (availableOnly && config.getLimit() >= 0 && ConfigLimit.getEnrollmentWeight(config, this) > config.getLimit()) 231 return; 232 if (availableOnly && course.getLimit() >= 0 && CourseLimit.getEnrollmentWeight(course, this) > course.getLimit()) 233 return; 234 if (config.getOffering().hasReservations()) { 235 boolean hasReservation = false, hasOtherReservation = false, hasConfigReservation = false, reservationMustBeUsed = false; 236 for (Reservation r: getReservations(course)) { 237 if (r.mustBeUsed()) reservationMustBeUsed = true; 238 if (availableOnly && r.getReservedAvailableSpace(this) < getWeight()) continue; 239 if (r.getConfigs().isEmpty()) { 240 hasReservation = true; 241 } else if (r.getConfigs().contains(config)) { 242 hasReservation = true; 243 hasConfigReservation = true; 244 } else { 245 hasOtherReservation = true; 246 } 247 } 248 if (!hasConfigReservation && config.getTotalUnreservedSpace() < getWeight()) 249 return; 250 if (!hasReservation && config.getOffering().getTotalUnreservedSpace() < getWeight()) 251 return; 252 if (hasOtherReservation && !hasReservation) 253 return; 254 if (availableOnly && !hasReservation && config.getOffering().getUnreservedSpace(this) < getWeight()) 255 return; 256 if (availableOnly && !hasConfigReservation && config.getUnreservedSpace(this) < getWeight()) 257 return; 258 if (!hasReservation && reservationMustBeUsed) 259 return; 260 } 261 } 262 } 263 if (config.getSubparts().size() == idx) { 264 if (skipSameTime && sSameTimePrecise) { 265 boolean waitListedOrSelected = false; 266 if (!getSelectedChoices().isEmpty() || !getWaitlistedChoices().isEmpty()) { 267 for (Section section : sections) { 268 if (isWaitlisted(section) || isSelected(section)) { 269 waitListedOrSelected = true; 270 break; 271 } 272 } 273 } 274 if (!waitListedOrSelected) { 275 for (Enrollment enrollment : enrollments) { 276 if (sameTimes(enrollment.getSections(), sections)) 277 return; 278 } 279 } 280 } 281 if (!config.getOffering().hasReservations()) { 282 enrollments.add(new Enrollment(this, priority, null, config, new HashSet<Assignment>(sections), null)); 283 } else { 284 Enrollment e = new Enrollment(this, priority, null, config, new HashSet<Assignment>(sections), null); 285 boolean mustHaveReservation = config.getOffering().getTotalUnreservedSpace() < getWeight(); 286 boolean mustHaveConfigReservation = config.getTotalUnreservedSpace() < getWeight(); 287 boolean mustHaveSectionReservation = false; 288 for (Section s: sections) { 289 if (s.getTotalUnreservedSpace() < getWeight()) { 290 mustHaveSectionReservation = true; 291 break; 292 } 293 } 294 boolean canOverLimit = false; 295 if (availableOnly && (getModel() == null || ((StudentSectioningModel)getModel()).getReservationCanAssignOverTheLimit())) { 296 for (Reservation r: getReservations(course)) { 297 if (!r.canAssignOverLimit() || !r.isIncluded(e)) continue; 298 if (r.getReservedAvailableSpace(this) < getWeight()) continue; 299 enrollments.add(new Enrollment(this, priority, null, config, new HashSet<Assignment>(sections), r)); 300 canOverLimit = true; 301 } 302 } 303 if (!canOverLimit) { 304 boolean reservationMustBeUsed = false; 305 reservations: for (Reservation r: (availableOnly ? new TreeSet<Reservation>(getReservations(course)) : getReservations(course))) { 306 if (r.mustBeUsed()) reservationMustBeUsed = true; 307 if (!r.isIncluded(e)) continue; 308 if (availableOnly && r.getReservedAvailableSpace(this) < getWeight()) continue; 309 if (mustHaveConfigReservation && r.getConfigs().isEmpty()) continue; 310 if (mustHaveSectionReservation) 311 for (Section s: sections) 312 if (r.getSections(s.getSubpart()) == null && s.getTotalUnreservedSpace() < getWeight()) continue reservations; 313 enrollments.add(new Enrollment(this, priority, null, config, new HashSet<Assignment>(sections), r)); 314 if (availableOnly) return; // only one available reservation suffice (the best matching one) 315 } 316 // a case w/o reservation 317 if (!(mustHaveReservation || mustHaveConfigReservation || mustHaveSectionReservation) && 318 !(availableOnly && config.getOffering().getUnreservedSpace(this) < getWeight()) && 319 !reservationMustBeUsed) { 320 enrollments.add(new Enrollment(this, (getReservations(course).isEmpty() ? 0 : 1) + priority, null, config, new HashSet<Assignment>(sections), null)); 321 } 322 } 323 } 324 } else { 325 Subpart subpart = config.getSubparts().get(idx); 326 HashSet<TimeLocation> times = (skipSameTime ? new HashSet<TimeLocation>() : null); 327 List<Section> sectionsThisSubpart = subpart.getSections(); 328 if (random) { 329 sectionsThisSubpart = new ArrayList<Section>(subpart.getSections()); 330 Collections.shuffle(sectionsThisSubpart); 331 } else if (skipSameTime) { 332 sectionsThisSubpart = new ArrayList<Section>(subpart.getSections()); 333 Collections.sort(sectionsThisSubpart); 334 } 335 boolean hasChildren = !subpart.getChildren().isEmpty(); 336 for (Section section : sectionsThisSubpart) { 337 if (section.getParent() != null && !sections.contains(section.getParent())) 338 continue; 339 if (section.isOverlapping(sections)) 340 continue; 341 if (selectedOnly && !isSelected(section)) 342 continue; 343 boolean canOverLimit = false; 344 if (availableOnly && (getModel() == null || ((StudentSectioningModel)getModel()).getReservationCanAssignOverTheLimit())) { 345 for (Reservation r: getReservations(course)) { 346 if (!r.canAssignOverLimit()) continue; 347 if (r.getSections(subpart) != null && !r.getSections(subpart).contains(section)) continue; 348 if (r.getReservedAvailableSpace(this) < getWeight()) continue; 349 canOverLimit = true; break; 350 } 351 } 352 if (!canOverLimit) { 353 if (availableOnly && section.getLimit() >= 0 354 && SectionLimit.getEnrollmentWeight(section, this) > section.getLimit()) 355 continue; 356 if (config.getOffering().hasReservations()) { 357 boolean hasReservation = false, hasSectionReservation = false, hasOtherReservation = false, reservationMustBeUsed = false; 358 for (Reservation r: getReservations(course)) { 359 if (r.mustBeUsed()) reservationMustBeUsed = true; 360 if (availableOnly && r.getReservedAvailableSpace(this) < getWeight()) continue; 361 if (r.getSections(subpart) == null) { 362 hasReservation = true; 363 } else if (r.getSections(subpart).contains(section)) { 364 hasReservation = true; 365 hasSectionReservation = true; 366 } else { 367 hasOtherReservation = true; 368 } 369 } 370 if (!hasSectionReservation && section.getTotalUnreservedSpace() < getWeight()) 371 continue; 372 if (hasOtherReservation && !hasReservation) 373 continue; 374 if (availableOnly && !hasSectionReservation && section.getUnreservedSpace(this) < getWeight()) 375 continue; 376 if (!hasReservation && reservationMustBeUsed) 377 continue; 378 } 379 } 380 if (skipSameTime && section.getTime() != null && !hasChildren && !times.add(section.getTime()) 381 && !isSelected(section) && !isWaitlisted(section)) 382 continue; 383 sections.add(section); 384 computeEnrollments(enrollments, priority, penalty + section.getPenalty(), course, config, sections, idx + 1, 385 availableOnly, skipSameTime, selectedOnly, random, limit); 386 sections.remove(section); 387 } 388 } 389 } 390 391 /** Return all enrollments that are available */ 392 public List<Enrollment> getAvaiableEnrollments() { 393 List<Enrollment> ret = new ArrayList<Enrollment>(); 394 int idx = 0; 395 for (Course course : iCourses) { 396 for (Config config : course.getOffering().getConfigs()) { 397 computeEnrollments(ret, idx, 0, course, config, new HashSet<Section>(), 0, true, false, false, false, -1); 398 } 399 idx++; 400 } 401 return ret; 402 } 403 404 /** 405 * Return all enrollments that are selected ( 406 * {@link CourseRequest#isSelected(Section)} is true) 407 * 408 * @param availableOnly 409 * pick only available sections 410 */ 411 public List<Enrollment> getSelectedEnrollments(boolean availableOnly) { 412 if (getSelectedChoices().isEmpty()) 413 return null; 414 Choice firstChoice = getSelectedChoices().iterator().next(); 415 List<Enrollment> enrollments = new ArrayList<Enrollment>(); 416 for (Course course : iCourses) { 417 if (!course.getOffering().equals(firstChoice.getOffering())) 418 continue; 419 for (Config config : course.getOffering().getConfigs()) { 420 computeEnrollments(enrollments, 0, 0, course, config, new HashSet<Section>(), 0, availableOnly, false, true, false, -1); 421 } 422 } 423 return enrollments; 424 } 425 426 /** 427 * Return all enrollments that are available, pick only the first section of 428 * the sections with the same time (of each subpart, {@link Section} 429 * comparator is used) 430 */ 431 public List<Enrollment> getAvaiableEnrollmentsSkipSameTime() { 432 List<Enrollment> avaiableEnrollmentsSkipSameTime = new ArrayList<Enrollment>(); 433 if (getInitialAssignment() != null) 434 avaiableEnrollmentsSkipSameTime.add(getInitialAssignment()); 435 int idx = 0; 436 for (Course course : iCourses) { 437 for (Config config : course.getOffering().getConfigs()) { 438 computeEnrollments(avaiableEnrollmentsSkipSameTime, idx, 0, course, config, new HashSet<Section>(), 0, true, true, false, false, -1); 439 } 440 idx++; 441 } 442 return avaiableEnrollmentsSkipSameTime; 443 } 444 445 /** 446 * Return all possible enrollments. 447 */ 448 public List<Enrollment> getEnrollmentsSkipSameTime() { 449 List<Enrollment> ret = new ArrayList<Enrollment>(); 450 int idx = 0; 451 for (Course course : iCourses) { 452 for (Config config : course.getOffering().getConfigs()) { 453 computeEnrollments(ret, idx, 0, course, config, new HashSet<Section>(), 0, false, true, false, false, -1); 454 } 455 idx++; 456 } 457 return ret; 458 } 459 460 /** Wait-listed choices */ 461 public Set<Choice> getWaitlistedChoices() { 462 return iWaitlistedChoices; 463 } 464 465 /** 466 * Return true when the given section is wait-listed (i.e., its choice is 467 * among wait-listed choices) 468 */ 469 public boolean isWaitlisted(Section section) { 470 return iWaitlistedChoices.contains(section.getChoice()); 471 } 472 473 /** Selected choices */ 474 public Set<Choice> getSelectedChoices() { 475 return iSelectedChoices; 476 } 477 478 /** 479 * Return true when the given section is selected (i.e., its choice is among 480 * selected choices) 481 */ 482 public boolean isSelected(Section section) { 483 return iSelectedChoices.contains(section.getChoice()); 484 } 485 486 /** 487 * Request name: A for alternative, 1 + priority, (w) when waitlist, list of 488 * course names 489 */ 490 @Override 491 public String getName() { 492 String ret = (isAlternative() ? "A" : "") 493 + (1 + getPriority() + (isAlternative() ? -getStudent().nrRequests() : 0)) + ". " 494 + (isWaitlist() ? "(w) " : ""); 495 int idx = 0; 496 for (Course course : iCourses) { 497 if (idx == 0) 498 ret += course.getName(); 499 else 500 ret += ", " + idx + ". alt " + course.getName(); 501 idx++; 502 } 503 return ret; 504 } 505 506 /** 507 * True if the student can be put on a wait-list (no alternative course 508 * request will be given instead) 509 */ 510 public boolean isWaitlist() { 511 return iWaitlist; 512 } 513 514 /** 515 * True if the student can be put on a wait-list (no alternative course 516 * request will be given instead) 517 */ 518 public void setWaitlist(boolean waitlist) { 519 iWaitlist = waitlist; 520 } 521 522 /** 523 * Time stamp of the request 524 */ 525 public Long getTimeStamp() { 526 return iTimeStamp; 527 } 528 529 @Override 530 public String toString() { 531 return getName() + (getWeight() != 1.0 ? " (W:" + sDF.format(getWeight()) + ")" : ""); 532 } 533 534 /** Return course of the requested courses with the given id */ 535 public Course getCourse(long courseId) { 536 for (Course course : iCourses) { 537 if (course.getId() == courseId) 538 return course; 539 } 540 return null; 541 } 542 543 /** Return configuration of the requested courses with the given id */ 544 public Config getConfig(long configId) { 545 for (Course course : iCourses) { 546 for (Config config : course.getOffering().getConfigs()) { 547 if (config.getId() == configId) 548 return config; 549 } 550 } 551 return null; 552 } 553 554 /** Return subpart of the requested courses with the given id */ 555 public Subpart getSubpart(long subpartId) { 556 for (Course course : iCourses) { 557 for (Config config : course.getOffering().getConfigs()) { 558 for (Subpart subpart : config.getSubparts()) { 559 if (subpart.getId() == subpartId) 560 return subpart; 561 } 562 } 563 } 564 return null; 565 } 566 567 /** Return section of the requested courses with the given id */ 568 public Section getSection(long sectionId) { 569 for (Course course : iCourses) { 570 for (Config config : course.getOffering().getConfigs()) { 571 for (Subpart subpart : config.getSubparts()) { 572 for (Section section : subpart.getSections()) { 573 if (section.getId() == sectionId) 574 return section; 575 } 576 } 577 } 578 } 579 return null; 580 } 581 582 /** 583 * Minimal penalty (minimum of {@link Offering#getMinPenalty()} among 584 * requested courses) 585 */ 586 public double getMinPenalty() { 587 if (iCachedMinPenalty == null) { 588 double min = Double.MAX_VALUE; 589 for (Course course : iCourses) { 590 min = Math.min(min, course.getOffering().getMinPenalty()); 591 } 592 iCachedMinPenalty = new Double(min); 593 } 594 return iCachedMinPenalty.doubleValue(); 595 } 596 597 /** 598 * Maximal penalty (maximum of {@link Offering#getMaxPenalty()} among 599 * requested courses) 600 */ 601 public double getMaxPenalty() { 602 if (iCachedMaxPenalty == null) { 603 double max = Double.MIN_VALUE; 604 for (Course course : iCourses) { 605 max = Math.max(max, course.getOffering().getMaxPenalty()); 606 } 607 iCachedMaxPenalty = new Double(max); 608 } 609 return iCachedMaxPenalty.doubleValue(); 610 } 611 612 /** Clear cached min/max penalties and cached bound */ 613 public void clearCache() { 614 iCachedMaxPenalty = null; 615 iCachedMinPenalty = null; 616 } 617 618 /** 619 * Estimated bound for this request -- it estimates the smallest value among 620 * all possible enrollments 621 */ 622 @Override 623 public double getBound() { 624 return - getWeight() * ((StudentSectioningModel)getModel()).getStudentWeights().getBound(this); 625 /* 626 if (iCachedBound == null) { 627 iCachedBound = new Double(-Math.pow(Enrollment.sPriorityWeight, getPriority()) 628 * (isAlternative() ? Enrollment.sAlterativeWeight : 1.0) 629 * Math.pow(Enrollment.sInitialWeight, (getInitialAssignment() == null ? 0 : 1)) 630 * Math.pow(Enrollment.sSelectedWeight, (iSelectedChoices.isEmpty() ? 0 : 1)) 631 * Math.pow(Enrollment.sWaitlistedWeight, (iWaitlistedChoices.isEmpty() ? 0 : 1)) 632 * 633 // Math.max(Enrollment.sMinWeight,getWeight()) * 634 (getStudent().isDummy() ? Student.sDummyStudentWeight : 1.0) 635 * Enrollment.normalizePenalty(getMinPenalty())); 636 } 637 return iCachedBound.doubleValue(); 638 */ 639 } 640 641 /** Return true if request is assigned. */ 642 @Override 643 public boolean isAssigned() { 644 return getAssignment() != null && !(getAssignment()).getAssignments().isEmpty(); 645 } 646 647 @Override 648 public boolean equals(Object o) { 649 return super.equals(o) && (o instanceof CourseRequest); 650 } 651 652 /** 653 * Get reservations for this course requests 654 */ 655 public List<Reservation> getReservations(Course course) { 656 if (iReservations == null) 657 iReservations = new HashMap<Course, List<Reservation>>(); 658 List<Reservation> reservations = iReservations.get(course); 659 if (reservations == null) { 660 reservations = new ArrayList<Reservation>(); 661 boolean mustBeUsed = false; 662 for (Reservation r: course.getOffering().getReservations()) { 663 if (!r.isApplicable(getStudent())) continue; 664 if (!mustBeUsed && r.mustBeUsed()) { reservations.clear(); mustBeUsed = true; } 665 if (mustBeUsed && !r.mustBeUsed()) continue; 666 reservations.add(r); 667 } 668 iReservations.put(course, reservations); 669 } 670 return reservations; 671 } 672 private Map<Course, List<Reservation>> iReservations = null; 673 674 /** 675 * Return true if there is a reservation for a course of this request 676 */ 677 public boolean hasReservations() { 678 for (Course course: getCourses()) 679 if (!getReservations(course).isEmpty()) 680 return true; 681 return false; 682 } 683 684 /** 685 * Clear reservation information that was cached on this section 686 */ 687 public void clearReservationCache() { 688 if (iReservations != null) iReservations.clear(); 689 } 690 691 }