001package org.cpsolver.exam.model;
002
003import java.util.ArrayList;
004import java.util.HashSet;
005import java.util.List;
006import java.util.Set;
007
008import org.cpsolver.ifs.assignment.Assignment;
009import org.cpsolver.ifs.model.Constraint;
010
011
012/**
013 * A student. <br>
014 * <br>
015 * 
016 * @version ExamTT 1.3 (Examination Timetabling)<br>
017 *          Copyright (C) 2008 - 2014 Tomas Muller<br>
018 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
019 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
020 * <br>
021 *          This library is free software; you can redistribute it and/or modify
022 *          it under the terms of the GNU Lesser General Public License as
023 *          published by the Free Software Foundation; either version 3 of the
024 *          License, or (at your option) any later version. <br>
025 * <br>
026 *          This library is distributed in the hope that it will be useful, but
027 *          WITHOUT ANY WARRANTY; without even the implied warranty of
028 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
029 *          Lesser General Public License for more details. <br>
030 * <br>
031 *          You should have received a copy of the GNU Lesser General Public
032 *          License along with this library; if not see
033 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
034 */
035public class ExamStudent extends Constraint<Exam, ExamPlacement> {
036    private boolean iAllowDirectConflicts = true;
037    private List<ExamOwner> iOwners = new ArrayList<ExamOwner>();
038    private boolean[] iAvailable = null;
039
040    /**
041     * Constructor
042     * 
043     * @param model
044     *            examination timetabling model
045     * @param id
046     *            student unique id
047     */
048    public ExamStudent(ExamModel model, long id) {
049        super();
050        iAllowDirectConflicts = model.getProperties().getPropertyBoolean("Student.AllowDirectConflicts", iAllowDirectConflicts);
051        iId = id;
052    }
053    
054    /**
055     * True if direct student conflicts are allowed for this student
056     * @return direct conflicts are allowed
057     */
058    public boolean isAllowDirectConflicts() {
059        return iAllowDirectConflicts;
060    }
061
062    /**
063     * Set whether direct student conflicts are allowed for this student
064     * @param allowDirectConflicts direct conflicts are allowed
065     */
066    public void setAllowDirectConflicts(boolean allowDirectConflicts) {
067        iAllowDirectConflicts = allowDirectConflicts;
068    }
069
070    /**
071     * True if the given two exams can have a direct student conflict with this
072     * student, i.e., they can be placed at the same period.
073     * 
074     * @param ex1
075     *            an exam
076     * @param ex2
077     *            an exam
078     * @return {@link ExamStudent#isAllowDirectConflicts()} and
079     *         {@link Exam#isAllowDirectConflicts()} for both exams
080     */
081    public boolean canConflict(Exam ex1, Exam ex2) {
082        return isAllowDirectConflicts() && ex1.isAllowDirectConflicts() && ex2.isAllowDirectConflicts();
083    }
084
085    /**
086     * Exam(s) enrolled by the student that are scheduled in the given period
087     * @param assignment current assignment
088     * @param period given period
089     * @return set of exams that this student is enrolled into and that are placed in the given period
090     */
091    public Set<Exam> getExams(Assignment<Exam, ExamPlacement> assignment, ExamPeriod period) {
092        Set<Exam> exams = ((ExamModel)getModel()).getStudentsOfPeriod(assignment, period).get(this);
093        return (exams != null ? exams : new HashSet<Exam>());
094        // return getContext(assignment).getExams(period.getIndex());
095    }
096
097    /**
098     * Exam(s) enrolled by the student that are scheduled in the given day
099     * @param assignment current assignment
100     * @param period given period
101     * @return set of exams that this student is enrolled into and that are placed in the day of the given period
102     */
103    public Set<Exam> getExamsADay(Assignment<Exam, ExamPlacement> assignment, ExamPeriod period) {
104        Set<Exam> exams = ((ExamModel)getModel()).getStudentsOfDay(assignment, period).get(this);
105        return (exams != null ? exams : new HashSet<Exam>());
106        // return getContext(assignment).getExamsOfDay(period.getDay());
107    }
108
109    /**
110     * Exam(s) enrolled by the student that are scheduled in the given day
111     * @param assignment current assignment
112     * @param day given day
113     * @return set of exams that this student is enrolled into and that are placed in the given day
114     */
115    public Set<Exam> getExamsADay(Assignment<Exam, ExamPlacement> assignment, int day) {
116        Set<Exam> exams = ((ExamModel)getModel()).getStudentsOfDay(assignment, day).get(this);
117        return (exams != null ? exams : new HashSet<Exam>());
118        // return getContext(assignment).getExamsOfDay(day);
119    }
120
121    /**
122     * Compute conflicts between the given assignment of an exam and all the
123     * current assignments (of this student). Only not-allowed conflicts (see
124     * {@link ExamStudent#canConflict(Exam, Exam)} are considered.
125     */
126    @Override
127    public void computeConflicts(Assignment<Exam, ExamPlacement> assignment, ExamPlacement p, Set<ExamPlacement> conflicts) {
128        Exam ex = p.variable();
129        Set<Exam> exams = ((ExamModel)getModel()).getStudentsOfPeriod(assignment, p.getPeriod()).get(this);
130        if (exams != null)
131            for (Exam exam : exams) {
132                if (!canConflict(ex, exam))
133                    conflicts.add(assignment.getValue(exam));
134            }
135    }
136
137    /**
138     * Check whether there is a conflict between the given assignment of an exam
139     * and all the current assignments (of this student). Only not-allowed
140     * conflicts (see {@link ExamStudent#canConflict(Exam, Exam)} are
141     * considered.
142     */
143    @Override
144    public boolean inConflict(Assignment<Exam, ExamPlacement> assignment, ExamPlacement p) {
145        Exam ex = p.variable();
146        Set<Exam> exams = ((ExamModel)getModel()).getStudentsOfPeriod(assignment, p.getPeriod()).get(this);
147        if (exams != null)
148            for (Exam exam : exams) {
149                if (!canConflict(ex, exam))
150                    return true;
151            }
152        return false;
153    }
154
155    /**
156     * True if the given exams can conflict (see
157     * {@link ExamStudent#canConflict(Exam, Exam)}), or if they are placed at
158     * different periods.
159     */
160    @Override
161    public boolean isConsistent(ExamPlacement p1, ExamPlacement p2) {
162        return (p1.getPeriod() != p2.getPeriod() || canConflict(p1.variable(), p2.variable()));
163    }
164
165    /**
166     * Compare two student for equality
167     */
168    @Override
169    public boolean equals(Object o) {
170        if (o == null || !(o instanceof ExamStudent))
171            return false;
172        ExamStudent s = (ExamStudent) o;
173        return getId() == s.getId();
174    }
175
176    /**
177     * Hash code
178     */
179    @Override
180    public int hashCode() {
181        return (int) (getId() ^ (getId() >>> 32));
182    }
183
184    /**
185     * Student unique id
186     */
187    @Override
188    public String toString() {
189        return String.valueOf(getId());
190    }
191
192    /**
193     * Compare two students (by student ids)
194     */
195    @Override
196    public int compareTo(Constraint<Exam, ExamPlacement> o) {
197        return toString().compareTo(o.toString());
198    }
199
200    /**
201     * Constraint is hard if {@link ExamStudent#isAllowDirectConflicts()} is
202     * false.
203     */
204    @Override
205    public boolean isHard() {
206        return !isAllowDirectConflicts();
207    }
208
209    /**
210     * Courses and/or sections that this student is enrolled to
211     * 
212     * @return list of {@link ExamOwner}
213     */
214    public List<ExamOwner> getOwners() {
215        return iOwners;
216    }
217
218    /**
219     * True if the student is available (for examination timetabling) during the
220     * given period
221     * 
222     * @param period
223     *            a period
224     * @return true if a student can attend an exam at the given period, false
225     *         if otherwise
226     */
227    public boolean isAvailable(ExamPeriod period) {
228        return (iAvailable == null ? true : iAvailable[period.getIndex()]);
229    }
230
231    /**
232     * Set whether the student is available (for examination timetabling) during
233     * the given period
234     * 
235     * @param period
236     *            a period
237     * @param available
238     *            true if a student can attend an exam at the given period,
239     *            false if otherwise
240     */
241    public void setAvailable(int period, boolean available) {
242        if (iAvailable == null) {
243            iAvailable = new boolean[((ExamModel)getModel()).getNrPeriods()];
244            for (int i = 0; i < iAvailable.length; i++)
245                iAvailable[i] = true;
246        }
247        iAvailable[period] = available;
248    }
249
250    /*
251    @Override
252    public Context createAssignmentContext(Assignment<Exam, ExamPlacement> assignment) {
253        return new Context(assignment);
254    }
255
256    public class Context implements AssignmentConstraintContext<Exam, ExamPlacement> {
257        private Set<Exam>[] iTable;
258        private Set<Exam>[] iDayTable;
259        
260        @SuppressWarnings("unchecked")
261        public Context(Assignment<Exam, ExamPlacement> assignment) {
262            ExamModel model = (ExamModel)getModel();
263            iTable = new Set[model.getNrPeriods()];
264            for (int i = 0; i < iTable.length; i++)
265                iTable[i] = new HashSet<Exam>();
266            iDayTable = new Set[model.getNrDays()];
267            for (int i = 0; i < iDayTable.length; i++)
268                iDayTable[i] = new HashSet<Exam>();
269            for (Exam exam: variables()) {
270                ExamPlacement placement = assignment.getValue(exam);
271                if (placement != null)
272                    assigned(assignment, placement);
273            }
274        }
275
276        @Override
277        public void assigned(Assignment<Exam, ExamPlacement> assignment, ExamPlacement placement) {
278            iTable[placement.getPeriod().getIndex()].add(placement.variable());
279            iDayTable[placement.getPeriod().getDay()].add(placement.variable());
280        }
281        
282        @Override
283        public void unassigned(Assignment<Exam, ExamPlacement> assignment, ExamPlacement placement) {
284            iTable[placement.getPeriod().getIndex()].remove(placement.variable());
285            iDayTable[placement.getPeriod().getDay()].remove(placement.variable());
286        }
287        
288        
289        public Set<Exam> getExams(int period) { return iTable[period]; }
290        
291        public Set<Exam> getExamsOfDay(int day) { return iDayTable[day]; }
292        
293    }
294    */
295}