    001package org.cpsolver.studentsct.model;
    002
    003import java.text.DecimalFormat;
    004import java.util.HashSet;
    005import java.util.Iterator;
    006import java.util.Set;
    007
    008import org.cpsolver.ifs.assignment.Assignment;
    009import org.cpsolver.ifs.model.Value;
    010import org.cpsolver.ifs.util.ToolBox;
    011import org.cpsolver.studentsct.StudentSectioningModel;
    012import org.cpsolver.studentsct.extension.DistanceConflict;
    013import org.cpsolver.studentsct.extension.StudentQuality;
    014import org.cpsolver.studentsct.extension.TimeOverlapsCounter;
    015import org.cpsolver.studentsct.reservation.Reservation;
    016
    017
    018/**
    019 * Representation of an enrollment of a student into a course. A student needs
    020 * to be enrolled in a section of each subpart of a selected configuration. When
    021 * parent-child relation is defined among sections, if a student is enrolled in
    022 * a section that has a parent section defined, he/she has be enrolled in the
    023 * parent section as well. Also, the selected sections cannot overlap in time. <br>
    024 * <br>
    025 *
    026 * @author  Tomáš Müller
    027 * @version StudentSct 1.3 (Student Sectioning)<br>
    028 *          Copyright (C) 2007 - 2014 Tomáš Müller<br>
    029 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    030 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    031 * <br>
    032 *          This library is free software; you can redistribute it and/or modify
    033 *          it under the terms of the GNU Lesser General Public License as
    034 *          published by the Free Software Foundation; either version 3 of the
    035 *          License, or (at your option) any later version. <br>
    036 * <br>
    037 *          This library is distributed in the hope that it will be useful, but
    038 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    039 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    040 *          Lesser General Public License for more details. <br>
    041 * <br>
    042 *          You should have received a copy of the GNU Lesser General Public
    043 *          License along with this library; if not see
    044 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    045 */
    046
    047public class Enrollment extends Value<Request, Enrollment> {
    048    private static DecimalFormat sDF = new DecimalFormat("0.000");
    049    private Request iRequest = null;
    050    private Config iConfig = null;
    051    private Course iCourse = null;
    052    private Set<? extends SctAssignment> iAssignments = null;
    053    private Double iCachedPenalty = null;
    054    private int iPriority = 0;
    055    private boolean iNoReservationPenalty = false;
    056    private Reservation iReservation = null;
    057    private Long iTimeStamp = null;
    058    private String iApproval = null;
    059
    060    /**
    061     * Constructor
    062     *
    063     * @param request
    064     *            course / free time request
    065     * @param priority
    066     *            zero for the course, one for the first alternative, two for the second alternative
    067     * @param noReservationPenalty
    068     *            when true +1 is added to priority (prefer enrollments with reservations)
    069     * @param course
    070     *            selected course
    071     * @param config
    072     *            selected configuration
    073     * @param assignments
    074     *            valid list of sections
    075     * @param reservation used reservation
    076     */
    077    public Enrollment(Request request, int priority, boolean noReservationPenalty, Course course, Config config, Set<? extends SctAssignment> assignments, Reservation reservation) {
    078        super(request);
    079        iRequest = request;
    080        iConfig = config;
    081        iAssignments = assignments;
    082        iPriority = priority;
    083        iCourse = course;
    084        iNoReservationPenalty = noReservationPenalty;
    085        if (iConfig != null && iCourse == null)
    086            for (Course c: ((CourseRequest)iRequest).getCourses()) {
    087                if (c.getOffering().getConfigs().contains(iConfig)) {
    088                    iCourse = c;
    089                    break;
    090                }
    091            }
    092        iReservation = reservation;
    093    }
    094
    095    /**
    096     * Constructor
    097     *
    098     * @param request
    099     *            course / free time request
    100     * @param priority
    101     *            zero for the course, one for the first alternative, two for the second alternative
    102     * @param course
    103     *            selected course
    104     * @param config
    105     *            selected configuration
    106     * @param assignments
    107     *            valid list of sections
    108     * @param reservation used reservation
    109     */
    110    public Enrollment(Request request, int priority, Course course, Config config, Set<? extends SctAssignment> assignments, Reservation reservation) {
    111        this(request, priority, false, course, config, assignments, reservation);
    112    }
    113
    114    /**
    115     * Constructor
    116     *
    117     * @param request
    118     *            course / free time request
    119     * @param priority
    120     *            zero for the course, one for the first alternative, two for the second alternative
    121     * @param config
    122     *            selected configuration
    123     * @param assignments
    124     *            valid list of sections
    125     * @param assignment current assignment (to guess the reservation)
    126     */
    127    public Enrollment(Request request, int priority, Config config, Set<? extends SctAssignment> assignments, Assignment<Request, Enrollment> assignment) {
    128        this(request, priority, null, config, assignments, null);
    129        if (assignments != null && assignment != null)
    130            guessReservation(assignment, true);
    131    }
    132
    133    /**
    134     * Guess the reservation based on the enrollment
    135     * @param assignment current assignment
    136     * @param onlyAvailable use only reservation that have some space left in them
    137     */
    138    public void guessReservation(Assignment<Request, Enrollment> assignment, boolean onlyAvailable) {
    139        if (iCourse != null) {
    140            Reservation best = null;
    141            for (Reservation reservation: ((CourseRequest)iRequest).getReservations(iCourse)) {
    142                if (reservation.isIncluded(this)) {
    143                    if (onlyAvailable && reservation.getContext(assignment).getReservedAvailableSpace(assignment, iConfig, iRequest) < iRequest.getWeight() && !reservation.canBatchAssignOverLimit())
    144                        continue;
    145                    if (best == null || best.getPriority() > reservation.getPriority()) {
    146                        best = reservation;
    147                    } else if (best.getPriority() == reservation.getPriority() &&
    148                        best.getContext(assignment).getReservedAvailableSpace(assignment, iConfig, iRequest) < reservation.getContext(assignment).getReservedAvailableSpace(assignment, iConfig, iRequest)) {
    149                        best = reservation;
    150                    }
    151                }
    152            }
    153            iReservation = best;
    154        }
    155    }
    156
    157    /** Student
    158     * @return student
    159     **/
    160    public Student getStudent() {
    161        return iRequest.getStudent();
    162    }
    163
    164    /** Request
    165     * @return request
    166     **/
    167    public Request getRequest() {
    168        return iRequest;
    169    }
    170
    171    /** True if the request is course request
    172     * @return true if the request if course request
    173     **/
    174    public boolean isCourseRequest() {
    175        return iConfig != null;
    176    }
    177
    178    /** Offering of the course request
    179     * @return offering of the course request
    180     **/
    181    public Offering getOffering() {
    182        return (iConfig == null ? null : iConfig.getOffering());
    183    }
    184
    185    /** Config of the course request
    186     * @return config of the course request
    187     **/
    188    public Config getConfig() {
    189        return iConfig;
    190    }
    191
    192    /** Course of the course request
    193     * @return course of the course request
    194     **/
    195    public Course getCourse() {
    196        return iCourse;
    197    }
    198
    199    /** List of assignments (selected sections)
    200     * @return assignments (selected sections)
    201     **/
    202    @SuppressWarnings("unchecked")
    203    public Set<SctAssignment> getAssignments() {
    204        return (Set<SctAssignment>) iAssignments;
    205    }
    206
    207    /** List of sections (only for course request)
    208     * @return selected sections
    209     **/
    210    @SuppressWarnings("unchecked")
    211    public Set<Section> getSections() {
    212        if (isCourseRequest())
    213            return (Set<Section>) iAssignments;
    214        return new HashSet<Section>();
    215    }
    216
    217    /** True when this enrollment is overlapping with the given enrollment
    218     * @param enrl other enrollment
    219     * @return true if there is an overlap
    220     **/
    221    public boolean isOverlapping(Enrollment enrl) {
    222        if (enrl == null || isAllowOverlap() || enrl.isAllowOverlap())
    223            return false;
    224        for (SctAssignment a : getAssignments()) {
    225            if (a.isOverlapping(enrl.getAssignments()))
    226                return true;
    227        }
    228        return false;
    229    }
    230
    231    /** Percent of sections that are wait-listed
    232     * @return percent of sections that are wait-listed
    233     **/
    234    public double percentWaitlisted() {
    235        if (!isCourseRequest())
    236            return 0.0;
    237        CourseRequest courseRequest = (CourseRequest) getRequest();
    238        int nrWaitlisted = 0;
    239        for (Section section : getSections()) {
    240            if (courseRequest.isWaitlisted(section))
    241                nrWaitlisted++;
    242        }
    243        return ((double) nrWaitlisted) / getAssignments().size();
    244    }
    245
    246    /** Percent of sections that are selected
    247     * @return percent of sections that are selected
    248     **/
    249    public double percentSelected() {
    250        if (!isCourseRequest())
    251            return 0.0;
    252        CourseRequest courseRequest = (CourseRequest) getRequest();
    253        int nrSelected = 0;
    254        for (Section section : getSections()) {
    255            if (courseRequest.isSelected(section))
    256                nrSelected++;
    257        }
    258        return ((double) nrSelected) / getAssignments().size();
    259    }
    260
    261    /** Percent of sections that are selected
    262     * @return percent of sections that are selected
    263     **/
    264    public double percentSelectedSameSection() {
    265        if (!isCourseRequest() || getStudent().isDummy()) return (getRequest().hasSelection() ? 1.0 : 0.0);
    266        CourseRequest courseRequest = (CourseRequest) getRequest();
    267        int nrSelected = 0;
    268        Set<Long> nrMatching = new HashSet<Long>();
    269        sections: for (Section section : getSections()) {
    270            for (Choice choice: courseRequest.getSelectedChoices()) {
    271                if (choice.getSubpartId() != null) nrMatching.add(choice.getSubpartId());
    272                if (choice.sameSection(section)) {
    273                    nrSelected ++; continue sections;
    274                }
    275            }
    276        }
    277        return (nrMatching.isEmpty() ? 1.0 : ((double) nrSelected) / nrMatching.size());
    278    }
    279
    280    /** Percent of sections that have the same configuration
    281     * @return percent of sections that are selected
    282     **/
    283    public double percentSelectedSameConfig() {
    284        if (!isCourseRequest() || getStudent().isDummy() || getConfig() == null) return (getRequest().hasSelection() ? 1.0 : 0.0);
    285        CourseRequest courseRequest = (CourseRequest) getRequest();
    286        boolean hasConfigSelection = false;
    287        for (Choice choice: courseRequest.getSelectedChoices()) {
    288            if (choice.getConfigId() != null) {
    289                hasConfigSelection = true;
    290                if (choice.getConfigId().equals(getConfig().getId())) return 1.0;
    291            }
    292        }
    293        return (hasConfigSelection ? 0.0 : 1.0);
    294    }
    295
    296    /** Percent of sections that are initial
    297     * @return percent of sections that of the initial enrollment
    298     **/
    299    public double percentInitial() {
    300        if (!isCourseRequest())
    301            return 0.0;
    302        if (getRequest().getInitialAssignment() == null)
    303            return 0.0;
    304        Enrollment inital = getRequest().getInitialAssignment();
    305        int nrInitial = 0;
    306        for (Section section : getSections()) {
    307            if (inital.getAssignments().contains(section))
    308                nrInitial++;
    309        }
    310        return ((double) nrInitial) / getAssignments().size();
    311    }
    312
    313    /** Percent of sections that have same time as the initial assignment
    314     * @return percent of sections that have same time as the initial assignment
    315     **/
    316    public double percentSameTime() {
    317        if (!isCourseRequest())
    318            return 0.0;
    319        Enrollment ie = getRequest().getInitialAssignment();
    320        if (ie != null) {
    321            int nrInitial = 0;
    322            sections: for (Section section : getSections()) {
    323                for (Section initial: ie.getSections()) {
    324                    if (section.sameInstructionalType(initial) && section.sameTime(initial)) {
    325                        nrInitial ++;
    326                        continue sections;
    327                    }
    328                }
    329            }
    330            return ((double) nrInitial) / getAssignments().size();
    331        }
    332        Set<Choice> selected = ((CourseRequest)getRequest()).getSelectedChoices();
    333        if (!selected.isEmpty()) {
    334            int nrInitial = 0;
    335            sections: for (Section section : getSections()) {
    336                for (Choice choice: selected) {
    337                    if (choice.sameOffering(section) && choice.sameInstructionalType(section) && choice.sameTime(section)) {
    338                        nrInitial ++;
    339                        continue sections;
    340                    }
    341
    342                }
    343            }
    344            return ((double) nrInitial) / getAssignments().size();
    345        }
    346        return 0.0;
    347    }
    348
    349    /** True if all the sections are wait-listed
    350     * @return all the sections are wait-listed
    351     **/
    352    public boolean isWaitlisted() {
    353        if (!isCourseRequest())
    354            return false;
    355        CourseRequest courseRequest = (CourseRequest) getRequest();
    356        for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) {
    357            Section section = (Section) i.next();
    358            if (!courseRequest.isWaitlisted(section))
    359                return false;
    360        }
    361        return true;
    362    }
    363
    364    /** True if all the sections are selected
    365     * @return all the sections are selected
    366     **/
    367    public boolean isSelected() {
    368        if (!isCourseRequest())
    369            return false;
    370        CourseRequest courseRequest = (CourseRequest) getRequest();
    371        for (Section section : getSections()) {
    372            if (!courseRequest.isSelected(section))
    373                return false;
    374        }
    375        return true;
    376    }
    377
    378    public boolean isRequired() {
    379        if (!isCourseRequest())
    380            return false;
    381        CourseRequest courseRequest = (CourseRequest) getRequest();
    382        for (Section section : getSections()) {
    383            if (!courseRequest.isRequired(section))
    384                return false;
    385        }
    386        return true;
    387    }
    388
    389    /**
    390     * Enrollment penalty -- sum of section penalties (see
    391     * {@link Section#getPenalty()})
    392     * @return online penalty
    393     */
    394    public double getPenalty() {
    395        if (iCachedPenalty == null) {
    396            double penalty = 0.0;
    397            if (isCourseRequest()) {
    398                for (Section section : getSections()) {
    399                    penalty += section.getPenalty();
    400                }
    401            }
    402            iCachedPenalty = Double.valueOf(penalty / getAssignments().size());
    403        }
    404        return iCachedPenalty.doubleValue();
    405    }
    406
    407    /** Enrollment value */
    408    @Override
    409    public double toDouble(Assignment<Request, Enrollment> assignment) {
    410        return toDouble(assignment, true);
    411    }
    412
    413    /** Enrollment value
    414     * @param assignment current assignment
    415     * @param precise if false, distance conflicts and time overlaps are ignored (i.e., much faster, but less precise computation)
    416     * @return enrollment penalty
    417     **/
    418    public double toDouble(Assignment<Request, Enrollment> assignment, boolean precise) {
    419        if (precise) {
    420            StudentSectioningModel model = (StudentSectioningModel)variable().getModel();
    421            if (model.getStudentQuality() != null)
    422                return - getRequest().getWeight() * model.getStudentWeights().getWeight(assignment, this, studentQualityConflicts(assignment));
    423            else
    424                return - getRequest().getWeight() * model.getStudentWeights().getWeight(assignment, this, distanceConflicts(assignment), timeOverlappingConflicts(assignment));
    425        } else {
    426            Double value = (assignment == null ? null : variable().getContext(assignment).getLastWeight());
    427            if (value != null) return - value;
    428            return - getRequest().getWeight() * ((StudentSectioningModel)variable().getModel()).getStudentWeights().getWeight(assignment, this);
    429        }
    430    }
    431
    432    /** Enrollment name */
    433    @Override
    434    public String getName() {
    435        if (getRequest() instanceof CourseRequest) {
    436            Course course = null;
    437            CourseRequest courseRequest = (CourseRequest) getRequest();
    438            for (Course c : courseRequest.getCourses()) {
    439                if (c.getOffering().getConfigs().contains(getConfig())) {
    440                    course = c;
    441                    break;
    442                }
    443            }
    444            String ret = (course == null ? getConfig() == null ? "" : getConfig().getName() : course.getName());
    445            for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) {
    446                Section assignment = (Section) i.next();
    447                ret += "\n  " + assignment.getLongName(true) + (i.hasNext() ? "," : "");
    448            }
    449            return ret;
    450        } else if (getRequest() instanceof FreeTimeRequest) {
    451            return "Free Time " + ((FreeTimeRequest) getRequest()).getTime().getLongName(true);
    452        } else {
    453            String ret = "";
    454            for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) {
    455                SctAssignment assignment = i.next();
    456                ret += assignment.toString() + (i.hasNext() ? "," : "");
    457                if (i.hasNext())
    458                    ret += "\n  ";
    459            }
    460            return ret;
    461        }
    462    }
    463
    464    public String toString(Assignment<Request, Enrollment> a) {
    465        if (getAssignments().isEmpty()) return "not assigned";
    466        Set<DistanceConflict.Conflict> dc = distanceConflicts(a);
    467        Set<TimeOverlapsCounter.Conflict> toc = timeOverlappingConflicts(a);
    468        int share = 0;
    469        if (toc != null)
    470            for (TimeOverlapsCounter.Conflict c: toc)
    471                share += c.getShare();
    472        String ret = toDouble(a) + "/" + sDF.format(getRequest().getBound())
    473                + (getPenalty() == 0.0 ? "" : "/" + sDF.format(getPenalty()))
    474                + (dc == null || dc.isEmpty() ? "" : "/dc:" + dc.size())
    475                + (share <= 0 ? "" : "/toc:" + share);
    476        if (getRequest() instanceof CourseRequest) {
    477            double sameGroup = 0.0; int groupCount = 0;
    478            for (RequestGroup g: ((CourseRequest)getRequest()).getRequestGroups()) {
    479                if (g.getCourse().equals(getCourse())) {
    480                    sameGroup += g.getEnrollmentSpread(a, this, 1.0, 0.0);
    481                    groupCount ++;
    482                }
    483            }
    484            if (groupCount > 0)
    485                ret += "/g:" + sDF.format(sameGroup / groupCount);
    486        }
    487        if (getRequest() instanceof CourseRequest) {
    488            ret += " ";
    489            for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) {
    490                SctAssignment assignment = i.next();
    491                ret += assignment + (i.hasNext() ? ", " : "");
    492            }
    493        }
    494        if (getReservation() != null) ret = "(r) " + ret;
    495        return ret;
    496    }
    497
    498    @Override
    499    public String toString() {
    500        if (getAssignments().isEmpty()) return "not assigned";
    501        String ret = sDF.format(getRequest().getBound()) + (getPenalty() == 0.0 ? "" : "/" + sDF.format(getPenalty()));
    502        if (getRequest() instanceof CourseRequest) {
    503            ret += " ";
    504            for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) {
    505                SctAssignment assignment = i.next();
    506                ret += assignment + (i.hasNext() ? ", " : "");
    507            }
    508        }
    509        if (getReservation() != null) ret = "(r) " + ret;
    510        if (getRequest().isMPP()) ret += " [i" + sDF.format(100.0 * percentInitial()) + "/t" + sDF.format(100.0 * percentSameTime()) + "]";
    511        return ret;
    512    }
    513
    514    @Override
    515    public boolean equals(Object o) {
    516        if (o == null || !(o instanceof Enrollment))
    517            return false;
    518        Enrollment e = (Enrollment) o;
    519        if (!ToolBox.equals(getCourse(), e.getCourse()))
    520            return false;
    521        if (!ToolBox.equals(getConfig(), e.getConfig()))
    522            return false;
    523        if (!ToolBox.equals(getRequest(), e.getRequest()))
    524            return false;
    525        if (!ToolBox.equals(getAssignments(), e.getAssignments()))
    526            return false;
    527        if (!ToolBox.equals(getReservation(), e.getReservation()))
    528            return false;
    529        return true;
    530    }
    531
    532    /** Distance conflicts, in which this enrollment is involved.
    533     * @param assignment current assignment
    534     * @return distance conflicts
    535     **/
    536    public Set<DistanceConflict.Conflict> distanceConflicts(Assignment<Request, Enrollment> assignment) {
    537        if (!isCourseRequest())
    538            return null;
    539        if (getRequest().getModel() instanceof StudentSectioningModel) {
    540            DistanceConflict dc = ((StudentSectioningModel) getRequest().getModel()).getDistanceConflict();
    541            if (dc == null) return null;
    542            return dc.allConflicts(assignment, this);
    543        } else
    544            return null;
    545    }
    546
    547    /** Time overlapping conflicts, in which this enrollment is involved.
    548     * @param assignment current assignment
    549     * @return time overlapping conflicts
    550     **/
    551    public Set<TimeOverlapsCounter.Conflict> timeOverlappingConflicts(Assignment<Request, Enrollment> assignment) {
    552        if (getRequest().getModel() instanceof StudentSectioningModel) {
    553            TimeOverlapsCounter toc = ((StudentSectioningModel) getRequest().getModel()).getTimeOverlaps();
    554            if (toc == null)
    555                return null;
    556            return toc.allConflicts(assignment, this);
    557        } else
    558            return null;
    559    }
    560
    561    public Set<StudentQuality.Conflict> studentQualityConflicts(Assignment<Request, Enrollment> assignment) {
    562        if (!isCourseRequest())
    563            return null;
    564        if (getRequest().getModel() instanceof StudentSectioningModel) {
    565            StudentQuality sq = ((StudentSectioningModel) getRequest().getModel()).getStudentQuality();
    566            if (sq == null) return null;
    567            return sq.allConflicts(assignment, this);
    568        } else
    569            return null;
    570    }
    571
    572    /**
    573     * Return enrollment priority
    574     * @return zero for the course, one for the first alternative, two for the second alternative
    575     */
    576    public int getPriority() {
    577        return iPriority + (iNoReservationPenalty ? 1 : 0);
    578    }
    579
    580    /**
    581     * Return enrollment priority, ignoring priority bump provided by reservations
    582     * @return zero for the course, one for the first alternative, two for the second alternative
    583     */
    584    public int getTruePriority() {
    585        return iPriority;
    586    }
    587
    588    /**
    589     * Return adjusted enrollment priority, including priority bump provided by reservations
    590     * (but ensuring that getting the course without a reservation is still better than getting an alternative)
    591     * @return zero for the course, two for the first alternative, four for the second alternative; plus one when the no reservation penalty applies
    592     */
    593    public int getAdjustedPriority() {
    594        return 2 * iPriority + (iNoReservationPenalty ? 1 : 0);
    595    }
    596
    597    /**
    598     * Return total number of slots of all sections in the enrollment.
    599     * @return number of slots used
    600     */
    601    public int getNrSlots() {
    602        int ret = 0;
    603        for (SctAssignment a: getAssignments()) {
    604            if (a.getTime() != null) ret += a.getTime().getLength() * a.getTime().getNrMeetings();
    605        }
    606        return ret;
    607    }
    608
    609    /**
    610     * Return reservation used for this enrollment
    611     * @return used reservation
    612     */
    613    public Reservation getReservation() { return iReservation; }
    614
    615    /**
    616     * Set reservation for this enrollment
    617     * @param reservation used reservation
    618     */
    619    public void setReservation(Reservation reservation) { iReservation = reservation; }
    620
    621    /**
    622     * Time stamp of the enrollment
    623     * @return enrollment time stamp
    624     */
    625    public Long getTimeStamp() {
    626        return iTimeStamp;
    627    }
    628
    629    /**
    630     * Time stamp of the enrollment
    631     * @param timeStamp enrollment time stamp
    632     */
    633    public void setTimeStamp(Long timeStamp) {
    634        iTimeStamp = timeStamp;
    635    }
    636
    637    /**
    638     * Approval of the enrollment (only used by the online student sectioning)
    639     * @return consent approval
    640     */
    641    public String getApproval() {
    642        return iApproval;
    643    }
    644
    645    /**
    646     * Approval of the enrollment (only used by the online student sectioning)
    647     * @param approval consent approval
    648     */
    649    public void setApproval(String approval) {
    650        iApproval = approval;
    651    }
    652
    653    /**
    654     * True if this enrollment can overlap with other enrollments of the student.
    655     * @return can overlap with other enrollments of the student
    656     */
    657    public boolean isAllowOverlap() {
    658        return (getReservation() != null && getReservation().isAllowOverlap());
    659    }
    660
    661    /**
    662     * Enrollment limit, i.e., the number of students that would be able to get into the offering using this enrollment (if all the sections are empty)
    663     * @return enrollment limit
    664     */
    665    public int getLimit() {
    666        if (!isCourseRequest()) return -1; // free time requests have no limit
    667        Integer limit = null;
    668        for (Section section: getSections())
    669            if (section.getLimit() >= 0) {
    670                if (limit == null)
    671                    limit = section.getLimit();
    672                else
    673                    limit = Math.min(limit, section.getLimit());
    674            }
    675        return (limit == null ? -1 : limit);
    676    }
    677
    678    /**
    679     * Credit of this enrollment (using either {@link Course#getCreditValue()} or {@link Subpart#getCreditValue()} when course credit is not present)
    680     * @return credit of this enrollment
    681     */
    682    public float getCredit() {
    683        if (getCourse() == null) return 0f;
    684        if (getAssignments().isEmpty()) return 0f;
    685        if (getCourse().hasCreditValue()) return getCourse().getCreditValue();
    686        float subpartCredit = 0f;
    687        for (Subpart subpart: getConfig().getSubparts()) {
    688            if (subpart.hasCreditValue()) subpartCredit += subpart.getCreditValue();
    689        }
    690        return subpartCredit;
    691    }
    692
    693}