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