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