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 Tomáš Müller<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        setModel(model);
053    }
054    
055    /**
056     * True if direct student conflicts are allowed for this student
057     * @return direct conflicts are allowed
058     */
059    public boolean isAllowDirectConflicts() {
060        return iAllowDirectConflicts;
061    }
062
063    /**
064     * Set whether direct student conflicts are allowed for this student
065     * @param allowDirectConflicts direct conflicts are allowed
066     */
067    public void setAllowDirectConflicts(boolean allowDirectConflicts) {
068        iAllowDirectConflicts = allowDirectConflicts;
069    }
070
071    /**
072     * True if the given two exams can have a direct student conflict with this
073     * student, i.e., they can be placed at the same period.
074     * 
075     * @param ex1
076     *            an exam
077     * @param ex2
078     *            an exam
079     * @return {@link ExamStudent#isAllowDirectConflicts()} and
080     *         {@link Exam#isAllowDirectConflicts()} for both exams
081     */
082    public boolean canConflict(Exam ex1, Exam ex2) {
083        return isAllowDirectConflicts() && ex1.isAllowDirectConflicts() && ex2.isAllowDirectConflicts();
084    }
085
086    /**
087     * Exam(s) enrolled by the student that are scheduled in the given period
088     * @param assignment current assignment
089     * @param period given period
090     * @return set of exams that this student is enrolled into and that are placed in the given period
091     */
092    public Set<Exam> getExams(Assignment<Exam, ExamPlacement> assignment, ExamPeriod period) {
093        Set<Exam> exams = ((ExamModel)getModel()).getStudentsOfPeriod(assignment, period).get(this);
094        return (exams != null ? exams : new HashSet<Exam>());
095        // return getContext(assignment).getExams(period.getIndex());
096    }
097
098    /**
099     * Exam(s) enrolled by the student that are scheduled in the given day
100     * @param assignment current assignment
101     * @param period given period
102     * @return set of exams that this student is enrolled into and that are placed in the day of the given period
103     */
104    public Set<Exam> getExamsADay(Assignment<Exam, ExamPlacement> assignment, ExamPeriod period) {
105        Set<Exam> exams = ((ExamModel)getModel()).getStudentsOfDay(assignment, period).get(this);
106        return (exams != null ? exams : new HashSet<Exam>());
107        // return getContext(assignment).getExamsOfDay(period.getDay());
108    }
109
110    /**
111     * Exam(s) enrolled by the student that are scheduled in the given day
112     * @param assignment current assignment
113     * @param day given day
114     * @return set of exams that this student is enrolled into and that are placed in the given day
115     */
116    public Set<Exam> getExamsADay(Assignment<Exam, ExamPlacement> assignment, int day) {
117        Set<Exam> exams = ((ExamModel)getModel()).getStudentsOfDay(assignment, day).get(this);
118        return (exams != null ? exams : new HashSet<Exam>());
119        // return getContext(assignment).getExamsOfDay(day);
120    }
121
122    /**
123     * Compute conflicts between the given assignment of an exam and all the
124     * current assignments (of this student). Only not-allowed conflicts (see
125     * {@link ExamStudent#canConflict(Exam, Exam)} are considered.
126     */
127    @Override
128    public void computeConflicts(Assignment<Exam, ExamPlacement> assignment, ExamPlacement p, Set<ExamPlacement> conflicts) {
129        Exam ex = p.variable();
130        Set<Exam> exams = ((ExamModel)getModel()).getStudentsOfPeriod(assignment, p.getPeriod()).get(this);
131        if (exams != null)
132            for (Exam exam : exams) {
133                if (!canConflict(ex, exam))
134                    conflicts.add(assignment.getValue(exam));
135            }
136    }
137
138    /**
139     * Check whether there is a conflict between the given assignment of an exam
140     * and all the current assignments (of this student). Only not-allowed
141     * conflicts (see {@link ExamStudent#canConflict(Exam, Exam)} are
142     * considered.
143     */
144    @Override
145    public boolean inConflict(Assignment<Exam, ExamPlacement> assignment, ExamPlacement p) {
146        Exam ex = p.variable();
147        Set<Exam> exams = ((ExamModel)getModel()).getStudentsOfPeriod(assignment, p.getPeriod()).get(this);
148        if (exams != null)
149            for (Exam exam : exams) {
150                if (!canConflict(ex, exam))
151                    return true;
152            }
153        return false;
154    }
155
156    /**
157     * True if the given exams can conflict (see
158     * {@link ExamStudent#canConflict(Exam, Exam)}), or if they are placed at
159     * different periods.
160     */
161    @Override
162    public boolean isConsistent(ExamPlacement p1, ExamPlacement p2) {
163        return (p1.getPeriod() != p2.getPeriod() || canConflict(p1.variable(), p2.variable()));
164    }
165
166    /**
167     * Compare two student for equality
168     */
169    @Override
170    public boolean equals(Object o) {
171        if (o == null || !(o instanceof ExamStudent))
172            return false;
173        ExamStudent s = (ExamStudent) o;
174        return getId() == s.getId();
175    }
176
177    /**
178     * Hash code
179     */
180    @Override
181    public int hashCode() {
182        return (int) (getId() ^ (getId() >>> 32));
183    }
184
185    /**
186     * Student unique id
187     */
188    @Override
189    public String toString() {
190        return String.valueOf(getId());
191    }
192
193    /**
194     * Compare two students (by student ids)
195     */
196    @Override
197    public int compareTo(Constraint<Exam, ExamPlacement> o) {
198        return toString().compareTo(o.toString());
199    }
200
201    /**
202     * Constraint is hard if {@link ExamStudent#isAllowDirectConflicts()} is
203     * false.
204     */
205    @Override
206    public boolean isHard() {
207        return !isAllowDirectConflicts();
208    }
209
210    /**
211     * Courses and/or sections that this student is enrolled to
212     * 
213     * @return list of {@link ExamOwner}
214     */
215    public List<ExamOwner> getOwners() {
216        return iOwners;
217    }
218
219    /**
220     * True if the student is available (for examination timetabling) during the
221     * given period
222     * 
223     * @param period
224     *            a period
225     * @return true if a student can attend an exam at the given period, false
226     *         if otherwise
227     */
228    public boolean isAvailable(ExamPeriod period) {
229        return (iAvailable == null ? true : iAvailable[period.getIndex()]);
230    }
231
232    /**
233     * Set whether the student is available (for examination timetabling) during
234     * the given period
235     * 
236     * @param period
237     *            a period
238     * @param available
239     *            true if a student can attend an exam at the given period,
240     *            false if otherwise
241     */
242    public void setAvailable(int period, boolean available) {
243        if (iAvailable == null) {
244            iAvailable = new boolean[((ExamModel)getModel()).getNrPeriods()];
245            for (int i = 0; i < iAvailable.length; i++)
246                iAvailable[i] = true;
247        }
248        iAvailable[period] = available;
249    }
250
251    /*
252    @Override
253    public Context createAssignmentContext(Assignment<Exam, ExamPlacement> assignment) {
254        return new Context(assignment);
255    }
256
257    public class Context implements AssignmentConstraintContext<Exam, ExamPlacement> {
258        private Set<Exam>[] iTable;
259        private Set<Exam>[] iDayTable;
260        
261        @SuppressWarnings("unchecked")
262        public Context(Assignment<Exam, ExamPlacement> assignment) {
263            ExamModel model = (ExamModel)getModel();
264            iTable = new Set[model.getNrPeriods()];
265            for (int i = 0; i < iTable.length; i++)
266                iTable[i] = new HashSet<Exam>();
267            iDayTable = new Set[model.getNrDays()];
268            for (int i = 0; i < iDayTable.length; i++)
269                iDayTable[i] = new HashSet<Exam>();
270            for (Exam exam: variables()) {
271                ExamPlacement placement = assignment.getValue(exam);
272                if (placement != null)
273                    assigned(assignment, placement);
274            }
275        }
276
277        @Override
278        public void assigned(Assignment<Exam, ExamPlacement> assignment, ExamPlacement placement) {
279            iTable[placement.getPeriod().getIndex()].add(placement.variable());
280            iDayTable[placement.getPeriod().getDay()].add(placement.variable());
281        }
282        
283        @Override
284        public void unassigned(Assignment<Exam, ExamPlacement> assignment, ExamPlacement placement) {
285            iTable[placement.getPeriod().getIndex()].remove(placement.variable());
286            iDayTable[placement.getPeriod().getDay()].remove(placement.variable());
287        }
288        
289        
290        public Set<Exam> getExams(int period) { return iTable[period]; }
291        
292        public Set<Exam> getExamsOfDay(int day) { return iDayTable[day]; }
293        
294    }
295    */
296}