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