001package org.cpsolver.studentsct.reservation; 002 003import java.util.HashMap; 004import java.util.HashSet; 005import java.util.Map; 006import java.util.Set; 007 008import org.cpsolver.ifs.assignment.Assignment; 009import org.cpsolver.ifs.assignment.AssignmentComparable; 010import org.cpsolver.ifs.assignment.context.AbstractClassWithContext; 011import org.cpsolver.ifs.assignment.context.AssignmentConstraintContext; 012import org.cpsolver.ifs.assignment.context.CanInheritContext; 013import org.cpsolver.ifs.model.Model; 014import org.cpsolver.studentsct.StudentSectioningModel; 015import org.cpsolver.studentsct.model.Config; 016import org.cpsolver.studentsct.model.Course; 017import org.cpsolver.studentsct.model.CourseRequest; 018import org.cpsolver.studentsct.model.Enrollment; 019import org.cpsolver.studentsct.model.Offering; 020import org.cpsolver.studentsct.model.Request; 021import org.cpsolver.studentsct.model.Section; 022import org.cpsolver.studentsct.model.Student; 023import org.cpsolver.studentsct.model.Subpart; 024 025 026 027/** 028 * Abstract reservation. This abstract class allow some section, courses, 029 * and other parts to be reserved to particular group of students. A reservation 030 * can be unlimited (any number of students of that particular group can attend 031 * a course, section, etc.) or with a limit (only given number of seats is 032 * reserved to the students of the particular group). 033 * 034 * <br> 035 * <br> 036 * 037 * @author Tomáš Müller 038 * @version StudentSct 1.3 (Student Sectioning)<br> 039 * Copyright (C) 2007 - 2014 Tomáš Müller<br> 040 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 041 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 042 * <br> 043 * This library is free software; you can redistribute it and/or modify 044 * it under the terms of the GNU Lesser General Public License as 045 * published by the Free Software Foundation; either version 3 of the 046 * License, or (at your option) any later version. <br> 047 * <br> 048 * This library is distributed in the hope that it will be useful, but 049 * WITHOUT ANY WARRANTY; without even the implied warranty of 050 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 051 * Lesser General Public License for more details. <br> 052 * <br> 053 * You should have received a copy of the GNU Lesser General Public 054 * License along with this library; if not see 055 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 056 */ 057public abstract class Reservation extends AbstractClassWithContext<Request, Enrollment, Reservation.ReservationContext> 058 implements AssignmentComparable<Reservation, Request, Enrollment>, CanInheritContext<Request, Enrollment, Reservation.ReservationContext> { 059 /** Reservation unique id */ 060 private long iId = 0; 061 062 /** Is reservation expired? */ 063 private boolean iExpired; 064 065 /** Instructional offering on which the reservation is set, required */ 066 private Offering iOffering; 067 068 /** One or more configurations, if applicable */ 069 private Set<Config> iConfigs = new HashSet<Config>(); 070 071 /** One or more sections, if applicable */ 072 private Map<Subpart, Set<Section>> iSections = new HashMap<Subpart, Set<Section>>(); 073 074 /** Reservation priority */ 075 private int iPriority = 100; 076 077 /** Must this reservation be used */ 078 private boolean iMustBeUsed = false; 079 080 /** Can assign over class / configuration / course limit */ 081 private boolean iCanAssignOverLimit = false; 082 083 /** Does this reservation allow for overlaps */ 084 private boolean iAllowOverlap = false; 085 086 /** Does this reservation allow for disabled sections */ 087 private boolean iAllowDisabled = false; 088 089 /** No enrollment is matching this reservation when set to true */ 090 private boolean iNeverIncluded = false; 091 092 /** Can break linked-sections constraint */ 093 private boolean iBreakLinkedSections = false; 094 095 096 /** 097 * Constructor 098 * @param id reservation unique id 099 * @param offering instructional offering on which the reservation is set 100 * @param priority reservation priority 101 * @param mustBeUsed must this reservation be used 102 * @param canAssignOverLimit can assign over class / configuration / course limit 103 * @param allowOverlap does this reservation allow for overlaps 104 */ 105 public Reservation(long id, Offering offering, int priority, boolean mustBeUsed, boolean canAssignOverLimit, boolean allowOverlap) { 106 iId = id; 107 iOffering = offering; 108 iOffering.getReservations().add(this); 109 iOffering.clearReservationCache(); 110 iPriority = priority; 111 iMustBeUsed = mustBeUsed; 112 iCanAssignOverLimit = canAssignOverLimit; 113 iAllowOverlap = allowOverlap; 114 } 115 116 /** 117 * Reservation id 118 * @return reservation unique id 119 */ 120 public long getId() { return iId; } 121 122 /** 123 * Reservation limit 124 * @return reservation limit, -1 for unlimited 125 */ 126 public abstract double getReservationLimit(); 127 128 129 /** Reservation priority (e.g., individual reservations first) 130 * @return reservation priority 131 **/ 132 public int getPriority() { 133 return iPriority; 134 } 135 136 /** 137 * Set reservation priority (e.g., individual reservations first) 138 * @param priority reservation priority 139 */ 140 public void setPriority(int priority) { 141 iPriority = priority; 142 } 143 144 /** 145 * Returns true if the student is applicable for the reservation 146 * @param student a student 147 * @param course a course 148 * @return true if student can use the reservation to get into the course / configuration / section 149 */ 150 public abstract boolean isApplicable(Student student, Course course); 151 152 @Deprecated 153 public boolean isApplicable(Student student) { 154 if (isApplicable(student, null)) return true; 155 for (Request r: student.getRequests()) { 156 if (r instanceof CourseRequest) { 157 for (Course course: ((CourseRequest) r).getCourses()) { 158 if (course.getOffering().equals(getOffering()) && isApplicable(student, course)) 159 return true; 160 } 161 } 162 } 163 return false; 164 } 165 166 /** 167 * Instructional offering on which the reservation is set. 168 * @return instructional offering 169 */ 170 public Offering getOffering() { return iOffering; } 171 172 /** 173 * One or more configurations on which the reservation is set (optional). 174 * @return instructional offering configurations 175 */ 176 public Set<Config> getConfigs() { return iConfigs; } 177 178 /** 179 * Add a configuration (of the offering {@link Reservation#getOffering()}) to this reservation 180 * @param config instructional offering configuration 181 */ 182 public void addConfig(Config config) { 183 iConfigs.add(config); 184 clearLimitCapCache(); 185 } 186 187 /** 188 * One or more sections on which the reservation is set (optional). 189 * @return class restrictions 190 */ 191 public Map<Subpart, Set<Section>> getSections() { return iSections; } 192 193 /** 194 * One or more sections on which the reservation is set (optional). 195 * @param subpart scheduling subpart 196 * @return class restrictions for the given scheduling subpart 197 */ 198 public Set<Section> getSections(Subpart subpart) { 199 return iSections.get(subpart); 200 } 201 202 /** 203 * Add a section (of the offering {@link Reservation#getOffering()}) to this reservation. 204 * This will also add all parent sections and the appropriate configuration to the offering. 205 * @param section a class restriction 206 */ 207 public void addSection(Section section, boolean inclusive) { 208 if (inclusive) { 209 addConfig(section.getSubpart().getConfig()); 210 while (section != null) { 211 Set<Section> sections = iSections.get(section.getSubpart()); 212 if (sections == null) { 213 sections = new HashSet<Section>(); 214 iSections.put(section.getSubpart(), sections); 215 } 216 sections.add(section); 217 section = section.getParent(); 218 } 219 } else { 220 Set<Section> sections = iSections.get(section.getSubpart()); 221 if (sections == null) { 222 sections = new HashSet<Section>(); 223 iSections.put(section.getSubpart(), sections); 224 } 225 sections.add(section); 226 } 227 clearLimitCapCache(); 228 } 229 230 public void addSection(Section section) { 231 addSection(section, true); 232 } 233 234 /** 235 * Return true if the given enrollment meets the reservation. 236 * @param enrollment given enrollment 237 * @return true if the given enrollment meets the reservation 238 */ 239 public boolean isIncluded(Enrollment enrollment) { 240 // Never included flag is set -- return false 241 if (neverIncluded()) return false; 242 243 // Free time request are never included 244 if (enrollment.getConfig() == null) return false; 245 246 // Check the offering 247 if (!iOffering.equals(enrollment.getConfig().getOffering())) return false; 248 249 if (areRestrictionsInclusive()) { 250 // If there are configurations, check the configuration 251 if (!iConfigs.isEmpty() && !iConfigs.contains(enrollment.getConfig())) return false; 252 253 // Check all the sections of the enrollment 254 for (Section section: enrollment.getSections()) { 255 Set<Section> sections = iSections.get(section.getSubpart()); 256 if (sections != null && !sections.contains(section)) 257 return false; 258 } 259 return true; 260 } else { 261 // no restrictions -> true 262 if (iConfigs.isEmpty() && iSections.isEmpty()) return true; 263 264 // configuration match -> true 265 if (iConfigs.contains(enrollment.getConfig())) return true; 266 267 // section match -> true 268 for (Section section: enrollment.getSections()) { 269 Set<Section> sections = iSections.get(section.getSubpart()); 270 if (sections != null && sections.contains(section)) 271 return true; 272 } 273 274 // no match -> false 275 return false; 276 } 277 } 278 279 /** 280 * True if the enrollment can be done using this reservation 281 * @param assignment current assignment 282 * @param enrollment given enrollment 283 * @return true if the given enrollment can be assigned 284 */ 285 public boolean canEnroll(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 286 // Check if student can use this reservation 287 if (!isApplicable(enrollment.getStudent(), enrollment.getCourse())) return false; 288 289 // Check if the enrollment meets the reservation 290 if (!isIncluded(enrollment)) return false; 291 292 // Check the limit 293 return getLimit(enrollment.getConfig()) < 0 || getContext(assignment).getUsedSpace() + enrollment.getRequest().getWeight() <= getLimit(enrollment.getConfig()); 294 } 295 296 /** 297 * True if can go over the course / config / section limit. Only to be used in the online sectioning. 298 * @return can assign over class / configuration / course limit 299 */ 300 public boolean canAssignOverLimit() { 301 return iCanAssignOverLimit; 302 } 303 304 /** 305 * True if the batch solver can assign the reservation over the course / config / section limit. 306 * @return {@link Reservation#canAssignOverLimit()} and {@link StudentSectioningModel#getReservationCanAssignOverTheLimit()} 307 */ 308 public boolean canBatchAssignOverLimit() { 309 return canAssignOverLimit() && (iOffering.getModel() == null || ((StudentSectioningModel)iOffering.getModel()).getReservationCanAssignOverTheLimit()); 310 } 311 312 /** 313 * Set to true if a student meeting this reservation can go over the course / config / section limit. 314 * @param canAssignOverLimit can assign over class / configuration / course limit 315 */ 316 public void setCanAssignOverLimit(boolean canAssignOverLimit) { 317 iCanAssignOverLimit = canAssignOverLimit; 318 } 319 320 /** 321 * If true, student must use the reservation (if applicable). Expired reservations do not need to be used. 322 * @return must this reservation be used 323 */ 324 public boolean mustBeUsed() { 325 return iMustBeUsed && !isExpired(); 326 } 327 328 /** 329 * If true, student must use the reservation (if applicable). Expiration date is ignored. 330 * @return must this reservation be used 331 */ 332 public boolean mustBeUsedIgnoreExpiration() { 333 return iMustBeUsed; 334 } 335 336 /** 337 * Set to true if the student must use the reservation (if applicable) 338 * @param mustBeUsed must this reservation be used 339 */ 340 public void setMustBeUsed(boolean mustBeUsed) { 341 iMustBeUsed = mustBeUsed; 342 } 343 344 /** 345 * Reservation restrictivity (estimated percentage of enrollments that include this reservation, 1.0 reservation on the whole offering) 346 * @return computed restrictivity 347 */ 348 public double getRestrictivity() { 349 if (iCachedRestrictivity == null) { 350 boolean inclusive = areRestrictionsInclusive(); 351 if (getConfigs().isEmpty() && getSections().isEmpty()) return 1.0; 352 int nrChoices = 0, nrMatchingChoices = 0; 353 for (Config config: getOffering().getConfigs()) { 354 int x[] = nrChoices(config, 0, new HashSet<Section>(), getConfigs().contains(config), inclusive); 355 nrChoices += x[0]; 356 nrMatchingChoices += x[1]; 357 } 358 iCachedRestrictivity = ((double)nrMatchingChoices) / nrChoices; 359 } 360 return iCachedRestrictivity; 361 } 362 private Double iCachedRestrictivity = null; 363 364 365 /** Number of choices and number of chaing choices in the given sub enrollment */ 366 private int[] nrChoices(Config config, int idx, HashSet<Section> sections, boolean matching, boolean inclusive) { 367 if (config.getSubparts().size() == idx) { 368 return new int[]{1, matching ? 1 : 0}; 369 } else { 370 Subpart subpart = config.getSubparts().get(idx); 371 Set<Section> matchingSections = getSections(subpart); 372 int choicesThisSubpart = 0; 373 int matchingChoicesThisSubpart = 0; 374 for (Section section : subpart.getSections()) { 375 if (section.getParent() != null && !sections.contains(section.getParent())) 376 continue; 377 if (section.isOverlapping(sections)) 378 continue; 379 sections.add(section); 380 boolean m = (inclusive 381 ? matching && (matchingSections == null || matchingSections.contains(section)) 382 : matching || (matchingSections != null && matchingSections.contains(section)) 383 ); 384 int[] x = nrChoices(config, 1 + idx, sections, m, inclusive); 385 choicesThisSubpart += x[0]; 386 matchingChoicesThisSubpart += x[1]; 387 sections.remove(section); 388 } 389 return new int[] {choicesThisSubpart, matchingChoicesThisSubpart}; 390 } 391 } 392 393 /** 394 * Priority first, than restrictivity (more restrictive first), than availability (more available first), than id 395 */ 396 @Override 397 public int compareTo(Assignment<Request, Enrollment> assignment, Reservation r) { 398 if (mustBeUsed() != r.mustBeUsed()) { 399 return (mustBeUsed() ? -1 : 1); 400 } 401 if (getPriority() != r.getPriority()) { 402 return (getPriority() < r.getPriority() ? -1 : 1); 403 } 404 int cmp = Double.compare(getRestrictivity(), r.getRestrictivity()); 405 if (cmp != 0) return cmp; 406 cmp = - Double.compare(getContext(assignment).getReservedAvailableSpace(assignment, null), r.getContext(assignment).getReservedAvailableSpace(assignment, null)); 407 if (cmp != 0) return cmp; 408 return Long.valueOf(getId()).compareTo(r.getId()); 409 } 410 411 /** 412 * Priority first, than restrictivity (more restrictive first), than id 413 */ 414 @Override 415 public int compareTo(Reservation r) { 416 if (mustBeUsed() != r.mustBeUsed()) { 417 return (mustBeUsed() ? -1 : 1); 418 } 419 if (getPriority() != r.getPriority()) { 420 return (getPriority() < r.getPriority() ? -1 : 1); 421 } 422 int cmp = Double.compare(getRestrictivity(), r.getRestrictivity()); 423 if (cmp != 0) return cmp; 424 return Long.valueOf(getId()).compareTo(r.getId()); 425 } 426 427 /** 428 * Return minimum of two limits where -1 counts as unlimited (any limit is smaller) 429 */ 430 private static double min(double l1, double l2) { 431 return (l1 < 0 ? l2 : l2 < 0 ? l1 : Math.min(l1, l2)); 432 } 433 434 /** 435 * Add two limits where -1 counts as unlimited (unlimited plus anything is unlimited) 436 */ 437 private static double add(double l1, double l2) { 438 return (l1 < 0 ? -1 : l2 < 0 ? -1 : l1 + l2); 439 } 440 441 442 /** Limit cap cache */ 443 private Double iLimitCap = null; 444 private Map<Long, Double> iConfigLimitCap = null; 445 446 /** 447 * Compute limit cap (maximum number of students that can get into the offering using this reservation) 448 * @return reservation limit cap 449 */ 450 public double getLimitCap() { 451 if (iLimitCap == null) iLimitCap = getLimitCapNoCache(); 452 return iLimitCap; 453 } 454 455 /** 456 * Check if restrictions are inclusive (that is for each section, the reservation also contains all its parents and the configuration) 457 */ 458 public boolean areRestrictionsInclusive() { 459 for (Map.Entry<Subpart, Set<Section>> entry: getSections().entrySet()) { 460 if (getConfigs().contains(entry.getKey().getConfig())) return true; 461 } 462 return false; 463 } 464 465 /** 466 * Compute limit cap (maximum number of students that can get into the offering using this reservation) 467 */ 468 private double getLimitCapNoCache() { 469 if (getConfigs().isEmpty() && getSections().isEmpty()) return -1; // no config -> can be unlimited 470 471 if (canAssignOverLimit()) return -1; // can assign over limit -> no cap 472 473 double cap = 0; 474 if (areRestrictionsInclusive()) { 475 // for each config 476 for (Config config: getConfigs()) { 477 // config cap 478 double configCap = config.getLimit(); 479 480 for (Map.Entry<Subpart, Set<Section>> entry: getSections().entrySet()) { 481 if (!config.equals(entry.getKey().getConfig())) continue; 482 Set<Section> sections = entry.getValue(); 483 484 // subpart cap 485 double subpartCap = 0; 486 for (Section section: sections) 487 subpartCap = add(subpartCap, section.getLimit()); 488 489 // minimize 490 configCap = min(configCap, subpartCap); 491 } 492 493 // add config cap 494 cap = add(cap, configCap); 495 } 496 } else { 497 // for each config 498 for (Config config: getConfigs()) 499 cap = add(cap, config.getLimit()); 500 // for each subpart 501 for (Map.Entry<Subpart, Set<Section>> entry: getSections().entrySet()) { 502 Set<Section> sections = entry.getValue(); 503 // subpart cap 504 double subpartCap = 0; 505 for (Section section: sections) 506 subpartCap = add(subpartCap, section.getLimit()); 507 cap = add(cap, subpartCap); 508 } 509 } 510 511 return cap; 512 } 513 514 /** 515 * Compute limit cap (maximum number of students that can get into the offering using this reservation) for a particular configuration 516 * @return reservation limit cap 517 */ 518 public double getLimitCap(Config config) { 519 Double cap = (iConfigLimitCap == null ? null : iConfigLimitCap.get(config.getId())); 520 if (cap == null) { 521 cap = getLimitCapNoCache(config); 522 if (iConfigLimitCap == null) iConfigLimitCap = new HashMap<Long, Double>(); 523 iConfigLimitCap.put(config.getId(), cap); 524 } 525 return cap; 526 } 527 528 private double getLimitCapNoCache(Config config) { 529 if (getConfigs().isEmpty() && getSections().isEmpty()) return -1; // no config -> can be unlimited 530 531 if (canAssignOverLimit()) return -1; // can assign over limit -> no cap 532 533 if (areRestrictionsInclusive()) { 534 // no restrictions for this configuration -> no limit 535 if (!getConfigs().contains(config)) return 0; 536 537 // config cap 538 double configCap = config.getLimit(); 539 540 for (Map.Entry<Subpart, Set<Section>> entry: getSections().entrySet()) { 541 if (!config.equals(entry.getKey().getConfig())) continue; 542 Set<Section> sections = entry.getValue(); 543 544 // subpart cap 545 double subpartCap = 0; 546 for (Section section: sections) 547 subpartCap = add(subpartCap, section.getLimit()); 548 549 // minimize 550 configCap = min(configCap, subpartCap); 551 } 552 553 // add config cap 554 return configCap; 555 } else { 556 double cap = 0; 557 558 // config cap 559 if (getConfigs().contains(config)) 560 cap = add(cap, config.getLimit()); 561 562 // for each subpart 563 for (Map.Entry<Subpart, Set<Section>> entry: getSections().entrySet()) { 564 if (!config.equals(entry.getKey().getConfig())) continue; 565 Set<Section> sections = entry.getValue(); 566 567 // subpart cap 568 double subpartCap = 0; 569 for (Section section: sections) 570 subpartCap = add(subpartCap, section.getLimit()); 571 cap = add(cap, subpartCap); 572 } 573 574 return cap; 575 } 576 577 } 578 579 /** 580 * Clear limit cap cache 581 */ 582 private void clearLimitCapCache() { 583 iLimitCap = null; 584 if (iConfigLimitCap != null) iConfigLimitCap.clear(); 585 } 586 587 /** 588 * Reservation limit capped the limit cap (see {@link Reservation#getLimitCap()}) 589 * @return reservation limit, -1 if unlimited 590 */ 591 public double getLimit() { 592 return min(getLimitCap(), getReservationLimit()); 593 } 594 595 /** 596 * Reservation limit capped the limit cap (see {@link Reservation#getLimitCap(Config)}) for a particular configuration 597 * @param config configuration for which the limit is computed (restrictions on other configurations are ignored) 598 * @return reservation limit, -1 if unlimited 599 */ 600 public double getLimit(Config config) { 601 return min(getLimitCap(config), getReservationLimit()); 602 } 603 604 /** 605 * True if holding this reservation allows a student to have attend overlapping class. 606 * @return does this reservation allow for overlaps 607 */ 608 public boolean isAllowOverlap() { 609 return iAllowOverlap; 610 } 611 612 /** 613 * Set to true if holding this reservation allows a student to have attend overlapping class. 614 * @param allowOverlap does this reservation allow for overlaps 615 */ 616 public void setAllowOverlap(boolean allowOverlap) { 617 iAllowOverlap = allowOverlap; 618 } 619 620 /** 621 * True if holding this reservation allows a student to attend a disabled class. 622 * @return does this reservation allow for disabled sections 623 */ 624 public boolean isAllowDisabled() { 625 return iAllowDisabled; 626 } 627 628 /** 629 * Set to true if holding this reservation allows a student to attend a disabled class 630 * @param allowDisabled does this reservation allow for disabled sections 631 */ 632 public void setAllowDisabled(boolean allowDisabled) { 633 iAllowDisabled = allowDisabled; 634 } 635 636 /** 637 * Set reservation expiration. If a reservation is expired, it works as ordinary reservation 638 * (especially the flags mutBeUsed and isAllowOverlap), except it does not block other students 639 * of getting into the offering / config / section. 640 * @param expired is this reservation expired 641 */ 642 public void setExpired(boolean expired) { 643 iExpired = expired; 644 } 645 646 /** 647 * True if the reservation is expired. If a reservation is expired, it works as ordinary reservation 648 * (especially the flags mutBeUsed and isAllowOverlap), except it does not block other students 649 * of getting into the offering / config / section. 650 * @return is this reservation expired 651 */ 652 public boolean isExpired() { 653 return iExpired; 654 } 655 656 /** 657 * No enrollment is matching this reservation when set to true 658 */ 659 public boolean neverIncluded() { return iNeverIncluded; } 660 661 /** 662 * No enrollment is matching this reservation when set to true 663 */ 664 public void setNeverIncluded(boolean neverIncluded) { iNeverIncluded = neverIncluded; } 665 666 /** 667 * Can break linked-section constraints when set to true 668 */ 669 public boolean canBreakLinkedSections() { return iBreakLinkedSections; } 670 671 /** 672 * Can break linked-section constraints when set to true 673 */ 674 public void setBreakLinkedSections(boolean breakLinkedSections) { iBreakLinkedSections = breakLinkedSections; } 675 676 677 @Override 678 public Model<Request, Enrollment> getModel() { 679 return getOffering().getModel(); 680 } 681 682 /** 683 * Available reserved space 684 * @param assignment current assignment 685 * @param excludeRequest excluding given request (if not null) 686 * @return available reserved space 687 **/ 688 public double getReservedAvailableSpace(Assignment<Request, Enrollment> assignment, Request excludeRequest) { 689 return getContext(assignment).getReservedAvailableSpace(assignment, excludeRequest); 690 } 691 692 /** 693 * Available reserved space for a particular config 694 * @param assignment current assignment 695 * @param excludeRequest excluding given request (if not null) 696 * @return available reserved space 697 **/ 698 public double getReservedAvailableSpace(Assignment<Request, Enrollment> assignment, Config config, Request excludeRequest) { 699 return getContext(assignment).getReservedAvailableSpace(assignment, config, excludeRequest); 700 } 701 702 /** Enrollments assigned using this reservation 703 * @param assignment current assignment 704 * @return assigned enrollments of this reservation 705 **/ 706 public Set<Enrollment> getEnrollments(Assignment<Request, Enrollment> assignment) { 707 return getContext(assignment).getEnrollments(); 708 } 709 710 @Override 711 public ReservationContext createAssignmentContext(Assignment<Request, Enrollment> assignment) { 712 return new ReservationContext(assignment); 713 } 714 715 716 @Override 717 public ReservationContext inheritAssignmentContext(Assignment<Request, Enrollment> assignment, ReservationContext parentContext) { 718 return new ReservationContext(parentContext); 719 } 720 721 722 public class ReservationContext implements AssignmentConstraintContext<Request, Enrollment> { 723 /** Enrollments included in this reservation */ 724 private Set<Enrollment> iEnrollments = new HashSet<Enrollment>(); 725 726 /** Used part of the limit */ 727 private double iUsed = 0; 728 private Map<Long, Double> iUsedByConfig = new HashMap<Long, Double>(); 729 private boolean iReadOnly = false; 730 731 public ReservationContext(Assignment<Request, Enrollment> assignment) { 732 for (Course course: getOffering().getCourses()) 733 for (CourseRequest request: course.getRequests()) { 734 Enrollment enrollment = assignment.getValue(request); 735 if (enrollment != null && Reservation.this.equals(enrollment.getReservation())) 736 assigned(assignment, enrollment); 737 } 738 } 739 740 public ReservationContext(ReservationContext parent) { 741 iUsed = parent.iUsed; 742 iUsedByConfig = new HashMap<Long, Double>(parent.iUsedByConfig); 743 iEnrollments = parent.iEnrollments; 744 iReadOnly = true; 745 } 746 747 /** Notify reservation about an unassignment */ 748 @Override 749 public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 750 if (iReadOnly) { 751 iEnrollments = new HashSet<Enrollment>(iEnrollments); 752 iReadOnly = false; 753 } 754 if (iEnrollments.add(enrollment)) { 755 iUsed += enrollment.getRequest().getWeight(); 756 Double used = iUsedByConfig.get(enrollment.getConfig().getId()); 757 iUsedByConfig.put(enrollment.getConfig().getId(), enrollment.getRequest().getWeight() + (used == null ? 0.0 : used.doubleValue())); 758 } 759 } 760 761 /** Notify reservation about an assignment */ 762 @Override 763 public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 764 if (iReadOnly) { 765 iEnrollments = new HashSet<Enrollment>(iEnrollments); 766 iReadOnly = false; 767 } 768 if (iEnrollments.remove(enrollment)) { 769 iUsed -= enrollment.getRequest().getWeight(); 770 Double used = iUsedByConfig.get(enrollment.getConfig().getId()); 771 iUsedByConfig.put(enrollment.getConfig().getId(), (used == null ? 0.0 : used.doubleValue()) - enrollment.getRequest().getWeight()); 772 } 773 } 774 775 /** Enrollments assigned using this reservation 776 * @return assigned enrollments of this reservation 777 **/ 778 public Set<Enrollment> getEnrollments() { 779 return iEnrollments; 780 } 781 782 /** Used space 783 * @return spaced used of this reservation 784 **/ 785 public double getUsedSpace() { 786 return iUsed; 787 } 788 789 /** Used space in a particular config 790 * @return spaced used of this reservation 791 **/ 792 public double getUsedSpace(Config config) { 793 Double used = iUsedByConfig.get(config.getId()); 794 return (used == null ? 0.0 : used.doubleValue()); 795 } 796 797 /** 798 * Available reserved space 799 * @param assignment current assignment 800 * @param excludeRequest excluding given request (if not null) 801 * @return available reserved space 802 **/ 803 public double getReservedAvailableSpace(Assignment<Request, Enrollment> assignment, Request excludeRequest) { 804 // Unlimited 805 if (getLimit() < 0) return Double.MAX_VALUE; 806 807 double reserved = getLimit() - getContext(assignment).getUsedSpace(); 808 if (excludeRequest != null && assignment.getValue(excludeRequest) != null && iEnrollments.contains(assignment.getValue(excludeRequest))) 809 reserved += excludeRequest.getWeight(); 810 811 return reserved; 812 } 813 814 /** 815 * Available reserved space for a particular config 816 * @param assignment current assignment 817 * @param excludeRequest excluding given request (if not null) 818 * @return available reserved space 819 **/ 820 public double getReservedAvailableSpace(Assignment<Request, Enrollment> assignment, Config config, Request excludeRequest) { 821 if (config == null) return getReservedAvailableSpace(assignment, excludeRequest); 822 823 // Unlimited 824 if (getLimit(config) < 0) return Double.MAX_VALUE; 825 826 double reserved = getLimit(config) - getContext(assignment).getUsedSpace(config); 827 if (excludeRequest != null && assignment.getValue(excludeRequest) != null && assignment.getValue(excludeRequest).getConfig().equals(config) && iEnrollments.contains(assignment.getValue(excludeRequest))) 828 reserved += excludeRequest.getWeight(); 829 830 return reserved; 831 } 832 } 833}