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