    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        if (variable().getStudent().getExternalId() != null)
    512            ret = "[" + variable().getStudent().getExternalId() + "] " + ret;
    513        return ret;
    514    }
    515
    516    @Override
    517    public boolean equals(Object o) {
    518        if (o == null || !(o instanceof Enrollment))
    519            return false;
    520        Enrollment e = (Enrollment) o;
    521        if (!ToolBox.equals(getCourse(), e.getCourse()))
    522            return false;
    523        if (!ToolBox.equals(getConfig(), e.getConfig()))
    524            return false;
    525        if (!ToolBox.equals(getRequest(), e.getRequest()))
    526            return false;
    527        if (!ToolBox.equals(getAssignments(), e.getAssignments()))
    528            return false;
    529        if (!ToolBox.equals(getReservation(), e.getReservation()))
    530            return false;
    531        return true;
    532    }
    533
    534    /** Distance conflicts, in which this enrollment is involved.
    535     * @param assignment current assignment
    536     * @return distance conflicts
    537     **/
    538    public Set<DistanceConflict.Conflict> distanceConflicts(Assignment<Request, Enrollment> assignment) {
    539        if (!isCourseRequest())
    540            return null;
    541        if (getRequest().getModel() instanceof StudentSectioningModel) {
    542            DistanceConflict dc = ((StudentSectioningModel) getRequest().getModel()).getDistanceConflict();
    543            if (dc == null) return null;
    544            return dc.allConflicts(assignment, this);
    545        } else
    546            return null;
    547    }
    548
    549    /** Time overlapping conflicts, in which this enrollment is involved.
    550     * @param assignment current assignment
    551     * @return time overlapping conflicts
    552     **/
    553    public Set<TimeOverlapsCounter.Conflict> timeOverlappingConflicts(Assignment<Request, Enrollment> assignment) {
    554        if (getRequest().getModel() instanceof StudentSectioningModel) {
    555            TimeOverlapsCounter toc = ((StudentSectioningModel) getRequest().getModel()).getTimeOverlaps();
    556            if (toc == null)
    557                return null;
    558            return toc.allConflicts(assignment, this);
    559        } else
    560            return null;
    561    }
    562
    563    public Set<StudentQuality.Conflict> studentQualityConflicts(Assignment<Request, Enrollment> assignment) {
    564        if (!isCourseRequest())
    565            return null;
    566        if (getRequest().getModel() instanceof StudentSectioningModel) {
    567            StudentQuality sq = ((StudentSectioningModel) getRequest().getModel()).getStudentQuality();
    568            if (sq == null) return null;
    569            return sq.allConflicts(assignment, this);
    570        } else
    571            return null;
    572    }
    573
    574    /**
    575     * Return enrollment priority
    576     * @return zero for the course, one for the first alternative, two for the second alternative
    577     */
    578    public int getPriority() {
    579        return iPriority + (iNoReservationPenalty ? 1 : 0);
    580    }
    581
    582    /**
    583     * Return enrollment priority, ignoring priority bump provided by reservations
    584     * @return zero for the course, one for the first alternative, two for the second alternative
    585     */
    586    public int getTruePriority() {
    587        return iPriority;
    588    }
    589
    590    /**
    591     * Return adjusted enrollment priority, including priority bump provided by reservations
    592     * (but ensuring that getting the course without a reservation is still better than getting an alternative)
    593     * @return zero for the course, two for the first alternative, four for the second alternative; plus one when the no reservation penalty applies
    594     */
    595    public int getAdjustedPriority() {
    596        return 2 * iPriority + (iNoReservationPenalty ? 1 : 0);
    597    }
    598
    599    /**
    600     * Return total number of slots of all sections in the enrollment.
    601     * @return number of slots used
    602     */
    603    public int getNrSlots() {
    604        int ret = 0;
    605        for (SctAssignment a: getAssignments()) {
    606            if (a.getTime() != null) ret += a.getTime().getLength() * a.getTime().getNrMeetings();
    607        }
    608        return ret;
    609    }
    610
    611    /**
    612     * Return reservation used for this enrollment
    613     * @return used reservation
    614     */
    615    public Reservation getReservation() { return iReservation; }
    616
    617    /**
    618     * Set reservation for this enrollment
    619     * @param reservation used reservation
    620     */
    621    public void setReservation(Reservation reservation) { iReservation = reservation; }
    622
    623    /**
    624     * Time stamp of the enrollment
    625     * @return enrollment time stamp
    626     */
    627    public Long getTimeStamp() {
    628        return iTimeStamp;
    629    }
    630
    631    /**
    632     * Time stamp of the enrollment
    633     * @param timeStamp enrollment time stamp
    634     */
    635    public void setTimeStamp(Long timeStamp) {
    636        iTimeStamp = timeStamp;
    637    }
    638
    639    /**
    640     * Approval of the enrollment (only used by the online student sectioning)
    641     * @return consent approval
    642     */
    643    public String getApproval() {
    644        return iApproval;
    645    }
    646
    647    /**
    648     * Approval of the enrollment (only used by the online student sectioning)
    649     * @param approval consent approval
    650     */
    651    public void setApproval(String approval) {
    652        iApproval = approval;
    653    }
    654
    655    /**
    656     * True if this enrollment can overlap with other enrollments of the student.
    657     * @return can overlap with other enrollments of the student
    658     */
    659    public boolean isAllowOverlap() {
    660        return (getReservation() != null && getReservation().isAllowOverlap());
    661    }
    662
    663    /**
    664     * 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)
    665     * @return enrollment limit
    666     */
    667    public int getLimit() {
    668        if (!isCourseRequest()) return -1; // free time requests have no limit
    669        Integer limit = null;
    670        for (Section section: getSections())
    671            if (section.getLimit() >= 0) {
    672                if (limit == null)
    673                    limit = section.getLimit();
    674                else
    675                    limit = Math.min(limit, section.getLimit());
    676            }
    677        return (limit == null ? -1 : limit);
    678    }
    679
    680    /**
    681     * Credit of this enrollment (using either {@link Course#getCreditValue()} or {@link Subpart#getCreditValue()} when course credit is not present)
    682     * @return credit of this enrollment
    683     */
    684    public float getCredit() {
    685        if (getCourse() == null) return 0f;
    686        if (getAssignments().isEmpty()) return 0f;
    687        if (getCourse().hasCreditValue()) return getCourse().getCreditValue();
    688        float subpartCredit = 0f;
    689        for (Subpart subpart: getConfig().getSubparts()) {
    690            if (subpart.hasCreditValue()) subpartCredit += subpart.getCreditValue();
    691        }
    692        return subpartCredit;
    693    }
    694
    695}