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}