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