    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}