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