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 * @return true if student can use the reservation to get into the course / configuration / section 148 */ 149 public abstract boolean isApplicable(Student student); 150 151 /** 152 * Instructional offering on which the reservation is set. 153 * @return instructional offering 154 */ 155 public Offering getOffering() { return iOffering; } 156 157 /** 158 * One or more configurations on which the reservation is set (optional). 159 * @return instructional offering configurations 160 */ 161 public Set<Config> getConfigs() { return iConfigs; } 162 163 /** 164 * Add a configuration (of the offering {@link Reservation#getOffering()}) to this reservation 165 * @param config instructional offering configuration 166 */ 167 public void addConfig(Config config) { 168 iConfigs.add(config); 169 clearLimitCapCache(); 170 } 171 172 /** 173 * One or more sections on which the reservation is set (optional). 174 * @return class restrictions 175 */ 176 public Map<Subpart, Set<Section>> getSections() { return iSections; } 177 178 /** 179 * One or more sections on which the reservation is set (optional). 180 * @param subpart scheduling subpart 181 * @return class restrictions for the given scheduling subpart 182 */ 183 public Set<Section> getSections(Subpart subpart) { 184 return iSections.get(subpart); 185 } 186 187 /** 188 * Add a section (of the offering {@link Reservation#getOffering()}) to this reservation. 189 * This will also add all parent sections and the appropriate configuration to the offering. 190 * @param section a class restriction 191 */ 192 public void addSection(Section section, boolean inclusive) { 193 if (inclusive) { 194 addConfig(section.getSubpart().getConfig()); 195 while (section != null) { 196 Set<Section> sections = iSections.get(section.getSubpart()); 197 if (sections == null) { 198 sections = new HashSet<Section>(); 199 iSections.put(section.getSubpart(), sections); 200 } 201 sections.add(section); 202 section = section.getParent(); 203 } 204 } else { 205 Set<Section> sections = iSections.get(section.getSubpart()); 206 if (sections == null) { 207 sections = new HashSet<Section>(); 208 iSections.put(section.getSubpart(), sections); 209 } 210 sections.add(section); 211 } 212 clearLimitCapCache(); 213 } 214 215 public void addSection(Section section) { 216 addSection(section, true); 217 } 218 219 /** 220 * Return true if the given enrollment meets the reservation. 221 * @param enrollment given enrollment 222 * @return true if the given enrollment meets the reservation 223 */ 224 public boolean isIncluded(Enrollment enrollment) { 225 // Never included flag is set -- return false 226 if (neverIncluded()) return false; 227 228 // Free time request are never included 229 if (enrollment.getConfig() == null) return false; 230 231 // Check the offering 232 if (!iOffering.equals(enrollment.getConfig().getOffering())) return false; 233 234 if (areRestrictionsInclusive()) { 235 // If there are configurations, check the configuration 236 if (!iConfigs.isEmpty() && !iConfigs.contains(enrollment.getConfig())) return false; 237 238 // Check all the sections of the enrollment 239 for (Section section: enrollment.getSections()) { 240 Set<Section> sections = iSections.get(section.getSubpart()); 241 if (sections != null && !sections.contains(section)) 242 return false; 243 } 244 return true; 245 } else { 246 // no restrictions -> true 247 if (iConfigs.isEmpty() && iSections.isEmpty()) return true; 248 249 // configuration match -> true 250 if (iConfigs.contains(enrollment.getConfig())) return true; 251 252 // section match -> true 253 for (Section section: enrollment.getSections()) { 254 Set<Section> sections = iSections.get(section.getSubpart()); 255 if (sections != null && sections.contains(section)) 256 return true; 257 } 258 259 // no match -> false 260 return false; 261 } 262 } 263 264 /** 265 * True if the enrollment can be done using this reservation 266 * @param assignment current assignment 267 * @param enrollment given enrollment 268 * @return true if the given enrollment can be assigned 269 */ 270 public boolean canEnroll(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 271 // Check if student can use this reservation 272 if (!isApplicable(enrollment.getStudent())) return false; 273 274 // Check if the enrollment meets the reservation 275 if (!isIncluded(enrollment)) return false; 276 277 // Check the limit 278 return getLimit(enrollment.getConfig()) < 0 || getContext(assignment).getUsedSpace() + enrollment.getRequest().getWeight() <= getLimit(enrollment.getConfig()); 279 } 280 281 /** 282 * True if can go over the course / config / section limit. Only to be used in the online sectioning. 283 * @return can assign over class / configuration / course limit 284 */ 285 public boolean canAssignOverLimit() { 286 return iCanAssignOverLimit; 287 } 288 289 /** 290 * True if the batch solver can assign the reservation over the course / config / section limit. 291 * @return {@link Reservation#canAssignOverLimit()} and {@link StudentSectioningModel#getReservationCanAssignOverTheLimit()} 292 */ 293 public boolean canBatchAssignOverLimit() { 294 return canAssignOverLimit() && (iOffering.getModel() == null || ((StudentSectioningModel)iOffering.getModel()).getReservationCanAssignOverTheLimit()); 295 } 296 297 /** 298 * Set to true if a student meeting this reservation can go over the course / config / section limit. 299 * @param canAssignOverLimit can assign over class / configuration / course limit 300 */ 301 public void setCanAssignOverLimit(boolean canAssignOverLimit) { 302 iCanAssignOverLimit = canAssignOverLimit; 303 } 304 305 /** 306 * If true, student must use the reservation (if applicable). Expired reservations do not need to be used. 307 * @return must this reservation be used 308 */ 309 public boolean mustBeUsed() { 310 return iMustBeUsed && !isExpired(); 311 } 312 313 /** 314 * If true, student must use the reservation (if applicable). Expiration date is ignored. 315 * @return must this reservation be used 316 */ 317 public boolean mustBeUsedIgnoreExpiration() { 318 return iMustBeUsed; 319 } 320 321 /** 322 * Set to true if the student must use the reservation (if applicable) 323 * @param mustBeUsed must this reservation be used 324 */ 325 public void setMustBeUsed(boolean mustBeUsed) { 326 iMustBeUsed = mustBeUsed; 327 } 328 329 /** 330 * Reservation restrictivity (estimated percentage of enrollments that include this reservation, 1.0 reservation on the whole offering) 331 * @return computed restrictivity 332 */ 333 public double getRestrictivity() { 334 if (iCachedRestrictivity == null) { 335 boolean inclusive = areRestrictionsInclusive(); 336 if (getConfigs().isEmpty() && getSections().isEmpty()) return 1.0; 337 int nrChoices = 0, nrMatchingChoices = 0; 338 for (Config config: getOffering().getConfigs()) { 339 int x[] = nrChoices(config, 0, new HashSet<Section>(), getConfigs().contains(config), inclusive); 340 nrChoices += x[0]; 341 nrMatchingChoices += x[1]; 342 } 343 iCachedRestrictivity = ((double)nrMatchingChoices) / nrChoices; 344 } 345 return iCachedRestrictivity; 346 } 347 private Double iCachedRestrictivity = null; 348 349 350 /** Number of choices and number of chaing choices in the given sub enrollment */ 351 private int[] nrChoices(Config config, int idx, HashSet<Section> sections, boolean matching, boolean inclusive) { 352 if (config.getSubparts().size() == idx) { 353 return new int[]{1, matching ? 1 : 0}; 354 } else { 355 Subpart subpart = config.getSubparts().get(idx); 356 Set<Section> matchingSections = getSections(subpart); 357 int choicesThisSubpart = 0; 358 int matchingChoicesThisSubpart = 0; 359 for (Section section : subpart.getSections()) { 360 if (section.getParent() != null && !sections.contains(section.getParent())) 361 continue; 362 if (section.isOverlapping(sections)) 363 continue; 364 sections.add(section); 365 boolean m = (inclusive 366 ? matching && (matchingSections == null || matchingSections.contains(section)) 367 : matching || (matchingSections != null && matchingSections.contains(section)) 368 ); 369 int[] x = nrChoices(config, 1 + idx, sections, m, inclusive); 370 choicesThisSubpart += x[0]; 371 matchingChoicesThisSubpart += x[1]; 372 sections.remove(section); 373 } 374 return new int[] {choicesThisSubpart, matchingChoicesThisSubpart}; 375 } 376 } 377 378 /** 379 * Priority first, than restrictivity (more restrictive first), than availability (more available first), than id 380 */ 381 @Override 382 public int compareTo(Assignment<Request, Enrollment> assignment, Reservation r) { 383 if (mustBeUsed() != r.mustBeUsed()) { 384 return (mustBeUsed() ? -1 : 1); 385 } 386 if (getPriority() != r.getPriority()) { 387 return (getPriority() < r.getPriority() ? -1 : 1); 388 } 389 int cmp = Double.compare(getRestrictivity(), r.getRestrictivity()); 390 if (cmp != 0) return cmp; 391 cmp = - Double.compare(getContext(assignment).getReservedAvailableSpace(assignment, null), r.getContext(assignment).getReservedAvailableSpace(assignment, null)); 392 if (cmp != 0) return cmp; 393 return Long.valueOf(getId()).compareTo(r.getId()); 394 } 395 396 /** 397 * Priority first, than restrictivity (more restrictive first), than id 398 */ 399 @Override 400 public int compareTo(Reservation r) { 401 if (mustBeUsed() != r.mustBeUsed()) { 402 return (mustBeUsed() ? -1 : 1); 403 } 404 if (getPriority() != r.getPriority()) { 405 return (getPriority() < r.getPriority() ? -1 : 1); 406 } 407 int cmp = Double.compare(getRestrictivity(), r.getRestrictivity()); 408 if (cmp != 0) return cmp; 409 return Long.valueOf(getId()).compareTo(r.getId()); 410 } 411 412 /** 413 * Return minimum of two limits where -1 counts as unlimited (any limit is smaller) 414 */ 415 private static double min(double l1, double l2) { 416 return (l1 < 0 ? l2 : l2 < 0 ? l1 : Math.min(l1, l2)); 417 } 418 419 /** 420 * Add two limits where -1 counts as unlimited (unlimited plus anything is unlimited) 421 */ 422 private static double add(double l1, double l2) { 423 return (l1 < 0 ? -1 : l2 < 0 ? -1 : l1 + l2); 424 } 425 426 427 /** Limit cap cache */ 428 private Double iLimitCap = null; 429 private Map<Long, Double> iConfigLimitCap = null; 430 431 /** 432 * Compute limit cap (maximum number of students that can get into the offering using this reservation) 433 * @return reservation limit cap 434 */ 435 public double getLimitCap() { 436 if (iLimitCap == null) iLimitCap = getLimitCapNoCache(); 437 return iLimitCap; 438 } 439 440 /** 441 * Check if restrictions are inclusive (that is for each section, the reservation also contains all its parents and the configuration) 442 */ 443 public boolean areRestrictionsInclusive() { 444 for (Map.Entry<Subpart, Set<Section>> entry: getSections().entrySet()) { 445 if (getConfigs().contains(entry.getKey().getConfig())) return true; 446 } 447 return false; 448 } 449 450 /** 451 * Compute limit cap (maximum number of students that can get into the offering using this reservation) 452 */ 453 private double getLimitCapNoCache() { 454 if (getConfigs().isEmpty() && getSections().isEmpty()) return -1; // no config -> can be unlimited 455 456 if (canAssignOverLimit()) return -1; // can assign over limit -> no cap 457 458 double cap = 0; 459 if (areRestrictionsInclusive()) { 460 // for each config 461 for (Config config: getConfigs()) { 462 // config cap 463 double configCap = config.getLimit(); 464 465 for (Map.Entry<Subpart, Set<Section>> entry: getSections().entrySet()) { 466 if (!config.equals(entry.getKey().getConfig())) continue; 467 Set<Section> sections = entry.getValue(); 468 469 // subpart cap 470 double subpartCap = 0; 471 for (Section section: sections) 472 subpartCap = add(subpartCap, section.getLimit()); 473 474 // minimize 475 configCap = min(configCap, subpartCap); 476 } 477 478 // add config cap 479 cap = add(cap, configCap); 480 } 481 } else { 482 // for each config 483 for (Config config: getConfigs()) 484 cap = add(cap, config.getLimit()); 485 // for each subpart 486 for (Map.Entry<Subpart, Set<Section>> entry: getSections().entrySet()) { 487 Set<Section> sections = entry.getValue(); 488 // subpart cap 489 double subpartCap = 0; 490 for (Section section: sections) 491 subpartCap = add(subpartCap, section.getLimit()); 492 cap = add(cap, subpartCap); 493 } 494 } 495 496 return cap; 497 } 498 499 /** 500 * Compute limit cap (maximum number of students that can get into the offering using this reservation) for a particular configuration 501 * @return reservation limit cap 502 */ 503 public double getLimitCap(Config config) { 504 Double cap = (iConfigLimitCap == null ? null : iConfigLimitCap.get(config.getId())); 505 if (cap == null) { 506 cap = getLimitCapNoCache(config); 507 if (iConfigLimitCap == null) iConfigLimitCap = new HashMap<Long, Double>(); 508 iConfigLimitCap.put(config.getId(), cap); 509 } 510 return cap; 511 } 512 513 private double getLimitCapNoCache(Config config) { 514 if (getConfigs().isEmpty() && getSections().isEmpty()) return -1; // no config -> can be unlimited 515 516 if (canAssignOverLimit()) return -1; // can assign over limit -> no cap 517 518 if (areRestrictionsInclusive()) { 519 // no restrictions for this configuration -> no limit 520 if (!getConfigs().contains(config)) return 0; 521 522 // config cap 523 double configCap = config.getLimit(); 524 525 for (Map.Entry<Subpart, Set<Section>> entry: getSections().entrySet()) { 526 if (!config.equals(entry.getKey().getConfig())) continue; 527 Set<Section> sections = entry.getValue(); 528 529 // subpart cap 530 double subpartCap = 0; 531 for (Section section: sections) 532 subpartCap = add(subpartCap, section.getLimit()); 533 534 // minimize 535 configCap = min(configCap, subpartCap); 536 } 537 538 // add config cap 539 return configCap; 540 } else { 541 double cap = 0; 542 543 // config cap 544 if (getConfigs().contains(config)) 545 cap = add(cap, config.getLimit()); 546 547 // for each subpart 548 for (Map.Entry<Subpart, Set<Section>> entry: getSections().entrySet()) { 549 if (!config.equals(entry.getKey().getConfig())) continue; 550 Set<Section> sections = entry.getValue(); 551 552 // subpart cap 553 double subpartCap = 0; 554 for (Section section: sections) 555 subpartCap = add(subpartCap, section.getLimit()); 556 cap = add(cap, subpartCap); 557 } 558 559 return cap; 560 } 561 562 } 563 564 /** 565 * Clear limit cap cache 566 */ 567 private void clearLimitCapCache() { 568 iLimitCap = null; 569 if (iConfigLimitCap != null) iConfigLimitCap.clear(); 570 } 571 572 /** 573 * Reservation limit capped the limit cap (see {@link Reservation#getLimitCap()}) 574 * @return reservation limit, -1 if unlimited 575 */ 576 public double getLimit() { 577 return min(getLimitCap(), getReservationLimit()); 578 } 579 580 /** 581 * Reservation limit capped the limit cap (see {@link Reservation#getLimitCap(Config)}) for a particular configuration 582 * @param config configuration for which the limit is computed (restrictions on other configurations are ignored) 583 * @return reservation limit, -1 if unlimited 584 */ 585 public double getLimit(Config config) { 586 return min(getLimitCap(config), getReservationLimit()); 587 } 588 589 /** 590 * True if holding this reservation allows a student to have attend overlapping class. 591 * @return does this reservation allow for overlaps 592 */ 593 public boolean isAllowOverlap() { 594 return iAllowOverlap; 595 } 596 597 /** 598 * Set to true if holding this reservation allows a student to have attend overlapping class. 599 * @param allowOverlap does this reservation allow for overlaps 600 */ 601 public void setAllowOverlap(boolean allowOverlap) { 602 iAllowOverlap = allowOverlap; 603 } 604 605 /** 606 * True if holding this reservation allows a student to attend a disabled class. 607 * @return does this reservation allow for disabled sections 608 */ 609 public boolean isAllowDisabled() { 610 return iAllowDisabled; 611 } 612 613 /** 614 * Set to true if holding this reservation allows a student to attend a disabled class 615 * @param allowDisabled does this reservation allow for disabled sections 616 */ 617 public void setAllowDisabled(boolean allowDisabled) { 618 iAllowDisabled = allowDisabled; 619 } 620 621 /** 622 * Set reservation expiration. If a reservation is expired, it works as ordinary reservation 623 * (especially the flags mutBeUsed and isAllowOverlap), except it does not block other students 624 * of getting into the offering / config / section. 625 * @param expired is this reservation expired 626 */ 627 public void setExpired(boolean expired) { 628 iExpired = expired; 629 } 630 631 /** 632 * True if the reservation is expired. If a reservation is expired, it works as ordinary reservation 633 * (especially the flags mutBeUsed and isAllowOverlap), except it does not block other students 634 * of getting into the offering / config / section. 635 * @return is this reservation expired 636 */ 637 public boolean isExpired() { 638 return iExpired; 639 } 640 641 /** 642 * No enrollment is matching this reservation when set to true 643 */ 644 public boolean neverIncluded() { return iNeverIncluded; } 645 646 /** 647 * No enrollment is matching this reservation when set to true 648 */ 649 public void setNeverIncluded(boolean neverIncluded) { iNeverIncluded = neverIncluded; } 650 651 /** 652 * Can break linked-section constraints when set to true 653 */ 654 public boolean canBreakLinkedSections() { return iBreakLinkedSections; } 655 656 /** 657 * Can break linked-section constraints when set to true 658 */ 659 public void setBreakLinkedSections(boolean breakLinkedSections) { iBreakLinkedSections = breakLinkedSections; } 660 661 662 @Override 663 public Model<Request, Enrollment> getModel() { 664 return getOffering().getModel(); 665 } 666 667 /** 668 * Available reserved space 669 * @param assignment current assignment 670 * @param excludeRequest excluding given request (if not null) 671 * @return available reserved space 672 **/ 673 public double getReservedAvailableSpace(Assignment<Request, Enrollment> assignment, Request excludeRequest) { 674 return getContext(assignment).getReservedAvailableSpace(assignment, excludeRequest); 675 } 676 677 /** 678 * Available reserved space for a particular config 679 * @param assignment current assignment 680 * @param excludeRequest excluding given request (if not null) 681 * @return available reserved space 682 **/ 683 public double getReservedAvailableSpace(Assignment<Request, Enrollment> assignment, Config config, Request excludeRequest) { 684 return getContext(assignment).getReservedAvailableSpace(assignment, config, excludeRequest); 685 } 686 687 /** Enrollments assigned using this reservation 688 * @param assignment current assignment 689 * @return assigned enrollments of this reservation 690 **/ 691 public Set<Enrollment> getEnrollments(Assignment<Request, Enrollment> assignment) { 692 return getContext(assignment).getEnrollments(); 693 } 694 695 @Override 696 public ReservationContext createAssignmentContext(Assignment<Request, Enrollment> assignment) { 697 return new ReservationContext(assignment); 698 } 699 700 701 @Override 702 public ReservationContext inheritAssignmentContext(Assignment<Request, Enrollment> assignment, ReservationContext parentContext) { 703 return new ReservationContext(parentContext); 704 } 705 706 707 public class ReservationContext implements AssignmentConstraintContext<Request, Enrollment> { 708 /** Enrollments included in this reservation */ 709 private Set<Enrollment> iEnrollments = new HashSet<Enrollment>(); 710 711 /** Used part of the limit */ 712 private double iUsed = 0; 713 private Map<Long, Double> iUsedByConfig = new HashMap<Long, Double>(); 714 private boolean iReadOnly = false; 715 716 public ReservationContext(Assignment<Request, Enrollment> assignment) { 717 for (Course course: getOffering().getCourses()) 718 for (CourseRequest request: course.getRequests()) { 719 Enrollment enrollment = assignment.getValue(request); 720 if (enrollment != null && Reservation.this.equals(enrollment.getReservation())) 721 assigned(assignment, enrollment); 722 } 723 } 724 725 public ReservationContext(ReservationContext parent) { 726 iUsed = parent.iUsed; 727 iUsedByConfig = new HashMap<Long, Double>(parent.iUsedByConfig); 728 iEnrollments = parent.iEnrollments; 729 iReadOnly = true; 730 } 731 732 /** Notify reservation about an unassignment */ 733 @Override 734 public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 735 if (iReadOnly) { 736 iEnrollments = new HashSet<Enrollment>(iEnrollments); 737 iReadOnly = false; 738 } 739 if (iEnrollments.add(enrollment)) { 740 iUsed += enrollment.getRequest().getWeight(); 741 Double used = iUsedByConfig.get(enrollment.getConfig().getId()); 742 iUsedByConfig.put(enrollment.getConfig().getId(), enrollment.getRequest().getWeight() + (used == null ? 0.0 : used.doubleValue())); 743 } 744 } 745 746 /** Notify reservation about an assignment */ 747 @Override 748 public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 749 if (iReadOnly) { 750 iEnrollments = new HashSet<Enrollment>(iEnrollments); 751 iReadOnly = false; 752 } 753 if (iEnrollments.remove(enrollment)) { 754 iUsed -= enrollment.getRequest().getWeight(); 755 Double used = iUsedByConfig.get(enrollment.getConfig().getId()); 756 iUsedByConfig.put(enrollment.getConfig().getId(), (used == null ? 0.0 : used.doubleValue()) - enrollment.getRequest().getWeight()); 757 } 758 } 759 760 /** Enrollments assigned using this reservation 761 * @return assigned enrollments of this reservation 762 **/ 763 public Set<Enrollment> getEnrollments() { 764 return iEnrollments; 765 } 766 767 /** Used space 768 * @return spaced used of this reservation 769 **/ 770 public double getUsedSpace() { 771 return iUsed; 772 } 773 774 /** Used space in a particular config 775 * @return spaced used of this reservation 776 **/ 777 public double getUsedSpace(Config config) { 778 Double used = iUsedByConfig.get(config.getId()); 779 return (used == null ? 0.0 : used.doubleValue()); 780 } 781 782 /** 783 * Available reserved space 784 * @param assignment current assignment 785 * @param excludeRequest excluding given request (if not null) 786 * @return available reserved space 787 **/ 788 public double getReservedAvailableSpace(Assignment<Request, Enrollment> assignment, Request excludeRequest) { 789 // Unlimited 790 if (getLimit() < 0) return Double.MAX_VALUE; 791 792 double reserved = getLimit() - getContext(assignment).getUsedSpace(); 793 if (excludeRequest != null && assignment.getValue(excludeRequest) != null && iEnrollments.contains(assignment.getValue(excludeRequest))) 794 reserved += excludeRequest.getWeight(); 795 796 return reserved; 797 } 798 799 /** 800 * Available reserved space for a particular config 801 * @param assignment current assignment 802 * @param excludeRequest excluding given request (if not null) 803 * @return available reserved space 804 **/ 805 public double getReservedAvailableSpace(Assignment<Request, Enrollment> assignment, Config config, Request excludeRequest) { 806 if (config == null) return getReservedAvailableSpace(assignment, excludeRequest); 807 808 // Unlimited 809 if (getLimit(config) < 0) return Double.MAX_VALUE; 810 811 double reserved = getLimit(config) - getContext(assignment).getUsedSpace(config); 812 if (excludeRequest != null && assignment.getValue(excludeRequest) != null && assignment.getValue(excludeRequest).getConfig().equals(config) && iEnrollments.contains(assignment.getValue(excludeRequest))) 813 reserved += excludeRequest.getWeight(); 814 815 return reserved; 816 } 817 } 818}