    001package org.cpsolver.studentsct.model;
    002
    003import java.util.ArrayList;
    004import java.util.HashSet;
    005import java.util.List;
    006import java.util.Set;
    007
    008import org.cpsolver.coursett.model.TimeLocation;
    009import org.cpsolver.ifs.assignment.Assignment;
    010import org.cpsolver.studentsct.constraint.LinkedSections;
    011import org.cpsolver.studentsct.model.Request.RequestPriority;
    012
    013
    014
    015/**
    016 * Representation of a student. Each student contains id, and a list of
    017 * requests. <br>
    018 * <br>
    019 * Last-like semester students are mark as dummy. Dummy students have lower
    020 * value and generally should not block "real" students from getting requested
    021 * courses. <br>
    022 * <br>
    023 *
    024 * @version StudentSct 1.3 (Student Sectioning)<br>
    025 *          Copyright (C) 2007 - 2014 Tomáš Müller<br>
    026 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    027 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    028 * <br>
    029 *          This library is free software; you can redistribute it and/or modify
    030 *          it under the terms of the GNU Lesser General Public License as
    031 *          published by the Free Software Foundation; either version 3 of the
    032 *          License, or (at your option) any later version. <br>
    033 * <br>
    034 *          This library is distributed in the hope that it will be useful, but
    035 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    036 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    037 *          Lesser General Public License for more details. <br>
    038 * <br>
    039 *          You should have received a copy of the GNU Lesser General Public
    040 *          License along with this library; if not see
    041 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    042 */
    043public class Student implements Comparable<Student> {
    044    private long iId;
    045    private String iExternalId = null, iName = null;
    046    private StudentPriority iPriority = StudentPriority.Normal;
    047    private List<Request> iRequests = new ArrayList<Request>();
    048    private List<AreaClassificationMajor> iMajors = new ArrayList<AreaClassificationMajor>();
    049    private List<AreaClassificationMajor> iMinors = new ArrayList<AreaClassificationMajor>();
    050    private List<LinkedSections> iLinkedSections = new ArrayList<LinkedSections>();
    051    private Set<String> iAccommodations = new HashSet<String>();
    052    private List<StudentGroup> iGroups = new ArrayList<StudentGroup>();
    053    private String iStatus = null;
    054    private Long iEmailTimeStamp = null;
    055    private List<Unavailability> iUnavailabilities = new ArrayList<Unavailability>();
    056    private boolean iNeedShortDistances = false;
    057    private boolean iAllowDisabled = false;
    058    private Float iMinCredit = null;
    059    private Float iMaxCredit = null;
    060    private List<Instructor> iAdvisors = new ArrayList<Instructor>();
    061
    062    /**
    063     * Constructor
    064     *
    065     * @param id
    066     *            student unique id
    067     */
    068    public Student(long id) {
    069        iId = id;
    070    }
    071
    072    /**
    073     * Constructor
    074     *
    075     * @param id
    076     *            student unique id
    077     * @param dummy
    078     *            dummy flag
    079     */
    080    public Student(long id, boolean dummy) {
    081        iId = id;
    082        iPriority = (dummy ? StudentPriority.Dummy : StudentPriority.Normal);
    083    }
    084
    085    /** Student unique id
    086     * @return student unique id
    087     **/
    088    public long getId() {
    089        return iId;
    090    }
    091
    092    /** Set student unique id
    093     * @param id student unique id
    094     **/
    095    public void setId(long id) {
    096        iId = id;
    097    }
    098
    099    /** Student's course and free time requests
    100     * @return student requests
    101     **/
    102    public List<Request> getRequests() {
    103        return iRequests;
    104    }
    105
    106    /** Number of requests (alternative requests are ignored)
    107     * @return number of non alternative student requests
    108     **/
    109    public int nrRequests() {
    110        int ret = 0;
    111        for (Request r : getRequests()) {
    112            if (!r.isAlternative())
    113                ret++;
    114        }
    115        return ret;
    116    }
    117
    118    /** Number of alternative requests
    119     * @return number of alternative student requests
    120     **/
    121    public int nrAlternativeRequests() {
    122        int ret = 0;
    123        for (Request r : getRequests()) {
    124            if (r.isAlternative())
    125                ret++;
    126        }
    127        return ret;
    128    }
    129
    130    /**
    131     * True if the given request can be assigned to the student. A request
    132     * cannot be assigned to a student when the student already has the desired
    133     * number of requests assigned (i.e., number of non-alternative course
    134     * requests).
    135     * @param assignment current assignment
    136     * @param request given request of this student
    137     * @return true if the given request can be assigned
    138     **/
    139    public boolean canAssign(Assignment<Request, Enrollment> assignment, Request request) {
    140        if (request.isAssigned(assignment))
    141            return true;
    142        int alt = 0;
    143        float credit = 0f;
    144        boolean found = false;
    145        for (Request r : getRequests()) {
    146            if (r.equals(request))
    147                found = true;
    148            boolean assigned = (r.isAssigned(assignment) || r.equals(request));
    149            boolean course = (r instanceof CourseRequest);
    150            boolean waitlist = (course && ((CourseRequest) r).isWaitlist());
    151            if (r.isAlternative()) {
    152                if (assigned || (!found && waitlist))
    153                    alt--;
    154            } else {
    155                if (course && !waitlist && !assigned)
    156                    alt++;
    157            }
    158            if (r.equals(request))
    159                credit += r.getMinCredit();
    160            else {
    161                Enrollment e = r.getAssignment(assignment);
    162                if (e != null) credit += e.getCredit();
    163            }
    164        }
    165        return (alt >= 0 && credit <= getMaxCredit());
    166    }
    167
    168    /**
    169     * True if the student has assigned the desired number of requests (i.e.,
    170     * number of non-alternative course requests).
    171     * @param assignment current assignment
    172     * @return true if this student has a complete schedule
    173     */
    174    public boolean isComplete(Assignment<Request, Enrollment> assignment) {
    175        int nrRequests = 0;
    176        int nrAssignedRequests = 0;
    177        float credit = 0f;
    178        Float minCredit = null;
    179        for (Request r : getRequests()) {
    180            if (!(r instanceof CourseRequest))
    181                continue; // ignore free times
    182            if (!r.isAlternative())
    183                nrRequests++;
    184            if (r.isAssigned(assignment))
    185                nrAssignedRequests++;
    186            Enrollment e = r.getAssignment(assignment);
    187            if (e != null) {
    188                credit += e.getCredit();
    189            } else if (r instanceof CourseRequest) {
    190                minCredit = (minCredit == null ? r.getMinCredit() : Math.min(minCredit, r.getMinCredit()));
    191            }
    192        }
    193        return nrAssignedRequests == nrRequests || credit + (minCredit == null ? 0f : minCredit.floatValue()) > getMaxCredit();
    194    }
    195
    196    /** Number of assigned COURSE requests
    197     * @param assignment current assignment
    198     * @return number of assigned course requests
    199     **/
    200    public int nrAssignedRequests(Assignment<Request, Enrollment> assignment) {
    201        int nrAssignedRequests = 0;
    202        for (Request r : getRequests()) {
    203            if (!(r instanceof CourseRequest))
    204                continue; // ignore free times
    205            if (r.isAssigned(assignment))
    206                nrAssignedRequests++;
    207        }
    208        return nrAssignedRequests;
    209    }
    210
    211    @Override
    212    public String toString() {
    213        return (isDummy() ? "D" : "") + "S[" + getId() + "]";
    214    }
    215
    216    /**
    217     * Student's dummy flag. Dummy students have lower value and generally
    218     * should not block "real" students from getting requested courses.
    219     * @return true if projected student
    220     */
    221    public boolean isDummy() {
    222        return iPriority == StudentPriority.Dummy;
    223    }
    224
    225    /**
    226     * Set student's dummy flag. Dummy students have lower value and generally
    227     * should not block "real" students from getting requested courses.
    228     * @param dummy projected student
    229     */
    230    public void setDummy(boolean dummy) {
    231        if (dummy)
    232            iPriority = StudentPriority.Dummy;
    233        else if (iPriority == StudentPriority.Dummy)
    234            iPriority = StudentPriority.Normal;
    235    }
    236
    237    /**
    238     * Student's priority. Priority students are to be assigned first.
    239     * @return student priority level
    240     */
    241    public StudentPriority getPriority() {
    242        return iPriority;
    243    }
    244
    245    /**
    246     * Set student's priority. Priority students are to be assigned first.
    247     * @param priority student priority level
    248     */
    249    public void setPriority(StudentPriority priority) {
    250        iPriority = priority;
    251    }
    252
    253    /**
    254     * Set student's priority. Priority students are to be assigned first.
    255     * @param priority true for priority student
    256     */
    257    @Deprecated
    258    public void setPriority(boolean priority) {
    259        if (priority)
    260            iPriority = StudentPriority.Priority;
    261        else if (StudentPriority.Normal.isHigher(this))
    262            iPriority = StudentPriority.Normal;
    263    }
    264
    265    /**
    266     * Student's priority. Priority students are to be assigned first.
    267     * @return true if priority student
    268     */
    269    @Deprecated
    270    public boolean isPriority() {
    271        return StudentPriority.Normal.isHigher(this);
    272    }
    273
    274
    275    /**
    276     * List of student groups ({@link StudentGroup}) for the given student
    277     * @return list of academic area abbreviation (group type) &amp; group code pairs
    278     */
    279    public List<StudentGroup> getGroups() {
    280        return iGroups;
    281    }
    282
    283    /**
    284     * List student accommodations
    285     * @return student accommodations
    286     */
    287    public Set<String> getAccommodations() {
    288        return iAccommodations;
    289    }
    290
    291    /**
    292     * List of academic area, classification, and major codes ({@link AreaClassificationMajor}) for the given student
    293     * @return list of academic area, classification, and major codes
    294     */
    295    public List<AreaClassificationMajor> getAreaClassificationMajors() {
    296        return iMajors;
    297    }
    298
    299    /**
    300     * List of academic area, classification, and minor codes ({@link AreaClassificationMajor}) for the given student
    301     * @return list of academic area, classification, and minor codes
    302     */
    303    public List<AreaClassificationMajor> getAreaClassificationMinors() {
    304        return iMinors;
    305    }
    306
    307    /**
    308     * List of student's advisors
    309     */
    310    public List<Instructor> getAdvisors() {
    311        return iAdvisors;
    312    }
    313
    314    /**
    315     * Compare two students for equality. Two students are considered equal if
    316     * they have the same id.
    317     */
    318    @Override
    319    public boolean equals(Object object) {
    320        if (object == null || !(object instanceof Student))
    321            return false;
    322        return getId() == ((Student) object).getId() && isDummy() == ((Student) object).isDummy();
    323    }
    324
    325    /**
    326     * Hash code (base only on student id)
    327     */
    328    @Override
    329    public int hashCode() {
    330        return (int) (iId ^ (iId >>> 32));
    331    }
    332
    333    /**
    334     * Count number of free time slots overlapping with the given enrollment
    335     * @param enrollment given enrollment
    336     * @return number of slots overlapping with a free time request
    337     */
    338    public int countFreeTimeOverlaps(Enrollment enrollment) {
    339        if (!enrollment.isCourseRequest()) return 0;
    340        int ret = 0;
    341        for (Section section: enrollment.getSections()) {
    342            TimeLocation time = section.getTime();
    343            if (time != null)
    344                ret += countFreeTimeOverlaps(time);
    345        }
    346        return ret;
    347    }
    348
    349    /**
    350     * Count number of free time slots overlapping with the given time
    351     * @param time given time
    352     * @return number of time slots overlapping with a free time request
    353     */
    354    public int countFreeTimeOverlaps(TimeLocation time) {
    355        int ret = 0;
    356        for (Request r: iRequests) {
    357            if (r instanceof FreeTimeRequest) {
    358                TimeLocation freeTime = ((FreeTimeRequest)r).getTime();
    359                if (time.hasIntersection(freeTime))
    360                    ret += freeTime.nrSharedHours(time) * freeTime.nrSharedDays(time);
    361            }
    362        }
    363        return ret;
    364    }
    365
    366    /**
    367     * Get student external id
    368     * @return student external unique id
    369     */
    370    public String getExternalId() { return iExternalId; }
    371    /**
    372     * Set student external id
    373     * @param externalId student external id
    374     */
    375    public void setExternalId(String externalId) { iExternalId = externalId; }
    376
    377    /**
    378     * Get student name
    379     * @return student name
    380     */
    381    public String getName() { return iName; }
    382    /**
    383     * Set student name
    384     * @param name student name
    385     */
    386    public void setName(String name) { iName = name; }
    387
    388    /**
    389     * Linked sections of this student
    390     * @return linked sections of this student
    391     */
    392    public List<LinkedSections> getLinkedSections() { return iLinkedSections; }
    393
    394    /**
    395     * Get student status (online sectioning only)
    396     * @return student sectioning status
    397     */
    398    public String getStatus() { return iStatus; }
    399    /**
    400     * Set student status
    401     * @param status student sectioning status
    402     */
    403    public void setStatus(String status) { iStatus = status; }
    404
    405    /**
    406     * Get last email time stamp (online sectioning only)
    407     * @return student email time stamp
    408     */
    409    public Long getEmailTimeStamp() { return iEmailTimeStamp; }
    410    /**
    411     * Set last email time stamp
    412     * @param emailTimeStamp student email time stamp
    413     */
    414    public void setEmailTimeStamp(Long emailTimeStamp) { iEmailTimeStamp = emailTimeStamp; }
    415
    416    @Override
    417    public int compareTo(Student s) {
    418        // priority students first, dummy students last
    419        if (getPriority() != s.getPriority())
    420            return (getPriority().ordinal() < s.getPriority().ordinal() ? -1 : 1);
    421        // then id
    422        return new Long(getId()).compareTo(s.getId());
    423    }
    424
    425    /**
    426     * List of student unavailabilities
    427     * @return student unavailabilities
    428     */
    429    public List<Unavailability> getUnavailabilities() { return iUnavailabilities; }
    430
    431    /**
    432     * Check if student is available during the given section
    433     * @param section given section
    434     * @return true, if available (the section cannot overlap and there is no overlapping unavailability that cannot overlap)
    435     */
    436    public boolean isAvailable(Section section) {
    437        if (section.isAllowOverlap() || section.getTime() == null) return true;
    438        for (Unavailability unavailability: getUnavailabilities())
    439            if (unavailability.isOverlapping(section)) return false;
    440        return true;
    441    }
    442
    443    /**
    444     * Check if student is available during the given enrollment
    445     * @param enrollment given enrollment
    446     * @return true, if available
    447     */
    448    public boolean isAvailable(Enrollment enrollment) {
    449        if (enrollment != null && enrollment.isCourseRequest() && !enrollment.isAllowOverlap())
    450            for (Section section: enrollment.getSections())
    451                if (!isAvailable(section)) return false;
    452        return true;
    453    }
    454
    455    /**
    456     * Return true if the student needs short distances. A different distance conflict checking is employed for such students.
    457     * @return true if the student needs short distances
    458     */
    459    public boolean isNeedShortDistances() {
    460        return iNeedShortDistances;
    461    }
    462
    463    /**
    464     * Set true if the student needs short distances. A different distance conflict checking is employed for such students.
    465     * @param needShortDistances true if the student needs short distances (default is false)
    466     */
    467    public void setNeedShortDistances(boolean needShortDistances) {
    468        iNeedShortDistances = needShortDistances;
    469    }
    470
    471    /**
    472     * True if student can be enrolled in disabled sections, regardless if his/her reservations
    473     * @return does this student allow for disabled sections
    474     */
    475    public boolean isAllowDisabled() {
    476        return iAllowDisabled;
    477    }
    478
    479    /**
    480     * Set to true  if student can be enrolled in disabled sections, regardless if his/her reservations
    481     * @param allowDisabled does this student allow for disabled sections
    482     */
    483    public void setAllowDisabled(boolean allowDisabled) {
    484        iAllowDisabled = allowDisabled;
    485    }
    486
    487    /**
    488     * True if student has min credit defined
    489     * @return true if min credit is set
    490     */
    491    public boolean hasMinCredit() { return iMinCredit != null; }
    492
    493    /**
    494     * Get student min credit (0 if not set)
    495     * return student min credit
    496     */
    497    public float getMinCredit() { return (iMinCredit == null ? 0 : iMinCredit.floatValue()); }
    498
    499    /**
    500     * Has student any critical course requests?
    501     * @return true if a student has at least one course request that is marked as critical
    502     */
    503    @Deprecated
    504    public boolean hasCritical() {
    505        for (Request r: iRequests)
    506            if (!r.isAlternative() && r.isCritical()) return true;
    507        return false;
    508    }
    509
    510    /**
    511     * Has student any critical course requests?
    512     * @return true if a student has at least one course request that is marked as critical
    513     */
    514    public boolean hasCritical(RequestPriority rp) {
    515        for (Request r: iRequests)
    516            if (!r.isAlternative() && rp.isCritical(r)) return true;
    517        return false;
    518    }
    519
    520    /**
    521     * Has student any unassigned critical course requests?
    522     * @return true if a student has at least one not-alternative course request that is marked as critical and that is not assigned
    523     */
    524    @Deprecated
    525    public boolean hasUnassignedCritical(Assignment<Request, Enrollment> assignment) {
    526        for (Request r: iRequests)
    527            if (!r.isAlternative() && r.isCritical() && assignment.getValue(r) == null) return true;
    528        return false;
    529    }
    530
    531    /**
    532     * Has student any unassigned critical course requests?
    533     * @return true if a student has at least one not-alternative course request that is marked as critical and that is not assigned
    534     */
    535    public boolean hasUnassignedCritical(Assignment<Request, Enrollment> assignment, RequestPriority rp) {
    536        for (Request r: iRequests)
    537            if (!r.isAlternative() && rp.isCritical(r) && assignment.getValue(r) == null) return true;
    538        return false;
    539    }
    540
    541    /**
    542     * Set student min credit (null if not set)
    543     * @param maxCredit student min credit
    544     */
    545    public void setMinCredit(Float maxCredit) { iMinCredit = maxCredit; }
    546
    547    /**
    548     * True if student has max credit defined
    549     * @return true if max credit is set
    550     */
    551    public boolean hasMaxCredit() { return iMaxCredit != null; }
    552
    553    /**
    554     * Get student max credit ({@link Float#MAX_VALUE} if not set)
    555     * return student max credit
    556     */
    557    public float getMaxCredit() { return (iMaxCredit == null ? Float.MAX_VALUE : iMaxCredit.floatValue()); }
    558
    559    /**
    560     * Set student max credit (null if not set)
    561     * @param maxCredit student max credit
    562     */
    563    public void setMaxCredit(Float maxCredit) { iMaxCredit = maxCredit; }
    564
    565    /**
    566     * Return the number of assigned credits of the student
    567     * @param assignment current assignment
    568     * @return total assigned credit using {@link Enrollment#getCredit()}
    569     */
    570    public float getAssignedCredit(Assignment<Request, Enrollment> assignment) {
    571        float credit = 0f;
    572        for (Request r: getRequests()) {
    573            Enrollment e = r.getAssignment(assignment);
    574            if (e != null) credit += e.getCredit();
    575        }
    576        return credit;
    577    }
    578
    579    /**
    580     * Student priority level. Higher priority students are to be assigned first.
    581     * The student priority is used to re-order students and assign them accoding
    582     * to their priority.
    583     */
    584    public static enum StudentPriority {
    585        Priority("P", 1.00),
    586        Senior("4", 0.70),
    587        Junior("3", 0.49),
    588        Sophomore("2", 0.33),
    589        Freshmen("1", 0.24),
    590        Normal("N", null), // this is the default priority
    591        Dummy("D", null), // dummy students priority
    592        ;
    593
    594        String iCode;
    595        Double iBoost;
    596        StudentPriority(String code, Double boost) {
    597            iCode = code;
    598            iBoost = boost;
    599        }
    600        public String code() { return iCode; }
    601        public Double getBoost() { return iBoost; }
    602
    603        public boolean isSameOrHigher(Student s) {
    604            return s.getPriority().ordinal() <= ordinal();
    605        }
    606        public boolean isHigher(Student s) {
    607            return ordinal() < s.getPriority().ordinal();
    608        }
    609        public boolean isSame(Student s) {
    610            return ordinal() == s.getPriority().ordinal();
    611        }
    612        public static StudentPriority getPriority(String value) {
    613            if ("true".equalsIgnoreCase(value)) return StudentPriority.Priority;
    614            if ("false".equalsIgnoreCase(value)) return StudentPriority.Normal;
    615            for (StudentPriority sp: StudentPriority.values()) {
    616                if (sp.name().equalsIgnoreCase(value)) return sp;
    617            }
    618            return StudentPriority.Normal;
    619        }
    620    }
    621
    622    /**
    623     * Check if a student has given accommodation
    624     * @param code accommodation reference code
    625     * @return true if present
    626     */
    627    public boolean hasAccommodation(String code) {
    628        return code != null && !code.isEmpty() && iAccommodations.contains(code);
    629    }
    630}