    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}