001package org.cpsolver.exam.model;
002
003import java.util.ArrayList;
004import java.util.Date;
005import java.util.HashSet;
006import java.util.HashMap;
007import java.util.Iterator;
008import java.util.List;
009import java.util.Map;
010import java.util.Set;
011import java.util.StringTokenizer;
012import java.util.TreeSet;
013
014
015import org.apache.logging.log4j.Logger;
016import org.cpsolver.coursett.IdConvertor;
017import org.cpsolver.exam.criteria.DistributionPenalty;
018import org.cpsolver.exam.criteria.ExamCriterion;
019import org.cpsolver.exam.criteria.ExamRotationPenalty;
020import org.cpsolver.exam.criteria.InstructorBackToBackConflicts;
021import org.cpsolver.exam.criteria.InstructorDirectConflicts;
022import org.cpsolver.exam.criteria.InstructorDistanceBackToBackConflicts;
023import org.cpsolver.exam.criteria.InstructorMoreThan2ADayConflicts;
024import org.cpsolver.exam.criteria.InstructorNotAvailableConflicts;
025import org.cpsolver.exam.criteria.LargeExamsPenalty;
026import org.cpsolver.exam.criteria.PeriodIndexPenalty;
027import org.cpsolver.exam.criteria.PeriodPenalty;
028import org.cpsolver.exam.criteria.PeriodSizePenalty;
029import org.cpsolver.exam.criteria.PerturbationPenalty;
030import org.cpsolver.exam.criteria.RoomPenalty;
031import org.cpsolver.exam.criteria.RoomPerturbationPenalty;
032import org.cpsolver.exam.criteria.RoomSizePenalty;
033import org.cpsolver.exam.criteria.RoomSplitDistancePenalty;
034import org.cpsolver.exam.criteria.RoomSplitPenalty;
035import org.cpsolver.exam.criteria.StudentBackToBackConflicts;
036import org.cpsolver.exam.criteria.StudentDirectConflicts;
037import org.cpsolver.exam.criteria.StudentDistanceBackToBackConflicts;
038import org.cpsolver.exam.criteria.StudentMoreThan2ADayConflicts;
039import org.cpsolver.exam.criteria.StudentNotAvailableConflicts;
040import org.cpsolver.ifs.assignment.Assignment;
041import org.cpsolver.ifs.assignment.context.ModelWithContext;
042import org.cpsolver.ifs.criteria.Criterion;
043import org.cpsolver.ifs.model.Constraint;
044import org.cpsolver.ifs.model.Model;
045import org.cpsolver.ifs.util.Callback;
046import org.cpsolver.ifs.util.DataProperties;
047import org.cpsolver.ifs.util.DistanceMetric;
048import org.cpsolver.ifs.util.ToolBox;
049import org.dom4j.Document;
050import org.dom4j.DocumentHelper;
051import org.dom4j.Element;
052
053/**
054 * Examination timetabling model. Exams {@link Exam} are modeled as variables,
055 * rooms {@link ExamRoom} and students {@link ExamStudent} as constraints.
056 * Assignment of an exam to time (modeled as non-overlapping periods
057 * {@link ExamPeriod}) and space (set of rooms) is modeled using values
058 * {@link ExamPlacement}. In order to be able to model individual period and
059 * room preferences, period and room assignments are wrapped with
060 * {@link ExamPeriodPlacement} and {@link ExamRoomPlacement} classes
061 * respectively. Moreover, additional distribution constraint
062 * {@link ExamDistributionConstraint} can be defined in the model. <br>
063 * <br>
064 * The objective function consists of the following criteria:
065 * <ul>
066 * <li>Direct student conflicts (a student is enrolled in two exams that are
067 * scheduled at the same period, weighted by Exams.DirectConflictWeight)
068 * <li>Back-to-Back student conflicts (a student is enrolled in two exams that
069 * are scheduled in consecutive periods, weighted by
070 * Exams.BackToBackConflictWeight). If Exams.IsDayBreakBackToBack is false,
071 * there is no conflict between the last period and the first period of
072 * consecutive days.
073 * <li>Distance Back-to-Back student conflicts (same as Back-to-Back student
074 * conflict, but the maximum distance between rooms in which both exam take
075 * place is greater than Exams.BackToBackDistance, weighted by
076 * Exams.DistanceBackToBackConflictWeight).
077 * <li>More than two exams a day (a student is enrolled in three exams that are
078 * scheduled at the same day, weighted by Exams.MoreThanTwoADayWeight).
079 * <li>Period penalty (total of period penalties
080 * {@link PeriodPenalty} of all assigned exams, weighted by
081 * Exams.PeriodWeight).
082 * <li>Room size penalty (total of room size penalties
083 * {@link RoomSizePenalty} of all assigned exams, weighted by
084 * Exams.RoomSizeWeight).
085 * <li>Room split penalty (total of room split penalties
086 * {@link RoomSplitPenalty} of all assigned exams, weighted
087 * by Exams.RoomSplitWeight).
088 * <li>Room penalty (total of room penalties
089 * {@link RoomPenalty} of all assigned exams, weighted by
090 * Exams.RoomWeight).
091 * <li>Distribution penalty (total of distribution constraint weights
092 * {@link ExamDistributionConstraint#getWeight()} of all soft distribution
093 * constraints that are not satisfied, i.e.,
094 * {@link ExamDistributionConstraint#isSatisfied(Assignment)} = false; weighted by
095 * Exams.DistributionWeight).
096 * <li>Direct instructor conflicts (an instructor is enrolled in two exams that
097 * are scheduled at the same period, weighted by
098 * Exams.InstructorDirectConflictWeight)
099 * <li>Back-to-Back instructor conflicts (an instructor is enrolled in two exams
100 * that are scheduled in consecutive periods, weighted by
101 * Exams.InstructorBackToBackConflictWeight). If Exams.IsDayBreakBackToBack is
102 * false, there is no conflict between the last period and the first period of
103 * consecutive days.
104 * <li>Distance Back-to-Back instructor conflicts (same as Back-to-Back
105 * instructor conflict, but the maximum distance between rooms in which both
106 * exam take place is greater than Exams.BackToBackDistance, weighted by
107 * Exams.InstructorDistanceBackToBackConflictWeight).
108 * <li>Room split distance penalty (if an examination is assigned between two or
109 * three rooms, distance between these rooms can be minimized using this
110 * criterion)
111 * <li>Front load penalty (large exams can be penalized if assigned on or after
112 * a certain period)
113 * </ul>
114 * 
115 * @version ExamTT 1.3 (Examination Timetabling)<br>
116 *          Copyright (C) 2008 - 2014 Tomáš Müller<br>
117 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
118 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
119 * <br>
120 *          This library is free software; you can redistribute it and/or modify
121 *          it under the terms of the GNU Lesser General Public License as
122 *          published by the Free Software Foundation; either version 3 of the
123 *          License, or (at your option) any later version. <br>
124 * <br>
125 *          This library is distributed in the hope that it will be useful, but
126 *          WITHOUT ANY WARRANTY; without even the implied warranty of
127 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
128 *          Lesser General Public License for more details. <br>
129 * <br>
130 *          You should have received a copy of the GNU Lesser General Public
131 *          License along with this library; if not see
132 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
133 */
134public class ExamModel extends ModelWithContext<Exam, ExamPlacement, ExamContext> {
135    private static Logger sLog = org.apache.logging.log4j.LogManager.getLogger(ExamModel.class);
136    private DataProperties iProperties = null;
137    private int iMaxRooms = 4;
138    private List<ExamPeriod> iPeriods = new ArrayList<ExamPeriod>();
139    private List<ExamRoom> iRooms = new ArrayList<ExamRoom>();
140    private List<ExamStudent> iStudents = new ArrayList<ExamStudent>();
141    private List<ExamDistributionConstraint> iDistributionConstraints = new ArrayList<ExamDistributionConstraint>();
142    private List<ExamInstructor> iInstructors = new ArrayList<ExamInstructor>();
143    private ExamRoomSharing iRoomSharing = null;
144    private boolean iCheckForPeriodOverlaps = false;
145
146    private DistanceMetric iDistanceMetric = null;
147
148    /**
149     * Constructor
150     * 
151     * @param properties
152     *            problem properties
153     */
154    public ExamModel(DataProperties properties) {
155        super();
156        iProperties = properties;
157        iMaxRooms = properties.getPropertyInt("Exams.MaxRooms", iMaxRooms);
158        iCheckForPeriodOverlaps = properties.getPropertyBoolean("Exams.CheckForPeriodOverlaps", iCheckForPeriodOverlaps);
159        iDistanceMetric = new DistanceMetric(properties);
160        String roomSharingClass = properties.getProperty("Exams.RoomSharingClass");
161        if (roomSharingClass != null) {
162            try {
163                iRoomSharing = (ExamRoomSharing)Class.forName(roomSharingClass).getConstructor(Model.class, DataProperties.class).newInstance(this, properties);
164            } catch (Exception e) {
165                sLog.error("Failed to instantiate room sharing class " + roomSharingClass + ", reason: " + e.getMessage());
166            }
167        }
168        
169        String criteria = properties.getProperty("Exams.Criteria",
170                StudentDirectConflicts.class.getName() + ";" +
171                StudentNotAvailableConflicts.class.getName() + ";" +
172                StudentBackToBackConflicts.class.getName() + ";" +
173                StudentDistanceBackToBackConflicts.class.getName() + ";" +
174                StudentMoreThan2ADayConflicts.class.getName() + ";" +
175                InstructorDirectConflicts.class.getName() + ";" +
176                InstructorNotAvailableConflicts.class.getName() + ";" +
177                InstructorBackToBackConflicts.class.getName() + ";" +
178                InstructorDistanceBackToBackConflicts.class.getName() + ";" +
179                InstructorMoreThan2ADayConflicts.class.getName() + ";" +
180                PeriodPenalty.class.getName() + ";" +
181                RoomPenalty.class.getName() + ";" +
182                DistributionPenalty.class.getName() + ";" +
183                RoomSplitPenalty.class.getName() + ";" +
184                RoomSplitDistancePenalty.class.getName() + ";" +
185                RoomSizePenalty.class.getName() + ";" +
186                ExamRotationPenalty.class.getName() + ";" +
187                LargeExamsPenalty.class.getName() + ";" +
188                PeriodSizePenalty.class.getName() + ";" +
189                PeriodIndexPenalty.class.getName() + ";" +
190                PerturbationPenalty.class.getName() + ";" +
191                RoomPerturbationPenalty.class.getName() + ";"
192                );
193        // Additional (custom) criteria
194        criteria += ";" + properties.getProperty("Exams.AdditionalCriteria", "");
195        for (String criterion: criteria.split("\\;")) {
196            if (criterion == null || criterion.isEmpty()) continue;
197            try {
198                @SuppressWarnings("unchecked")
199                Class<Criterion<Exam, ExamPlacement>> clazz = (Class<Criterion<Exam, ExamPlacement>>)Class.forName(criterion);
200                addCriterion(clazz.newInstance());
201            } catch (Exception e) {
202                sLog.error("Unable to use " + criterion + ": " + e.getMessage());
203            }
204        }
205    }
206    
207    public DistanceMetric getDistanceMetric() {
208        return iDistanceMetric;
209    }
210    
211    /**
212     * True if there is an examination sharing model
213     * @return true if there is an examination sharing model
214     */
215    public boolean hasRoomSharing() { return iRoomSharing != null; }
216    
217    /**
218     * Return examination room sharing model
219     * @return examination room sharing model, if set
220     */
221    public ExamRoomSharing getRoomSharing() { return iRoomSharing; }
222
223    /**
224     * Set examination sharing model
225     * @param sharing examination sharing model
226     */
227    public void setRoomSharing(ExamRoomSharing sharing) {
228        iRoomSharing = sharing;
229    }
230
231    /**
232     * Initialization of the model
233     */
234    public void init() {
235        for (Exam exam : variables()) {
236            for (ExamRoomPlacement room : exam.getRoomPlacements()) {
237                room.getRoom().addVariable(exam);
238            }
239        }
240    }
241
242    /**
243     * Default maximum number of rooms (can be set by problem property
244     * Exams.MaxRooms, or in the input xml file, property maxRooms)
245     * @return default maximum number of rooms for an exam
246     */
247    public int getMaxRooms() {
248        return iMaxRooms;
249    }
250
251    /**
252     * Default maximum number of rooms (can be set by problem property
253     * Exams.MaxRooms, or in the input xml file, property maxRooms)
254     * @param maxRooms default maximum number of rooms for an exam
255     */
256    public void setMaxRooms(int maxRooms) {
257        iMaxRooms = maxRooms;
258    }
259    
260    /**
261     * Check for examination periods that overlap with each other
262     * @return true if examination periods can overlap with each other
263     */
264    public boolean isCheckForPeriodOverlaps() { return iCheckForPeriodOverlaps; }
265    
266    /**
267     * Enable checking for period overlaps
268     */
269    public void setCheckForPeriodOverlaps(boolean check) {
270        iCheckForPeriodOverlaps = check;
271    }
272
273    /**
274     * Add a period
275     * 
276     * @param id
277     *            period unique identifier
278     * @param day
279     *            day (e.g., 07/12/10)
280     * @param time
281     *            (e.g., 8:00am-10:00am)
282     * @param length
283     *            length of period in minutes
284     * @param penalty
285     *            period penalty
286     * @return added period
287     */
288    public ExamPeriod addPeriod(Long id, String day, String time, int length, int penalty) {
289        ExamPeriod lastPeriod = (iPeriods.isEmpty() ? null : (ExamPeriod) iPeriods.get(iPeriods.size() - 1));
290        ExamPeriod p = new ExamPeriod(id, day, time, length, penalty);
291        if (lastPeriod == null)
292            p.setIndex(iPeriods.size(), 0, 0);
293        else if (lastPeriod.getDayStr().equals(day)) {
294            p.setIndex(iPeriods.size(), lastPeriod.getDay(), lastPeriod.getTime() + 1);
295        } else
296            p.setIndex(iPeriods.size(), lastPeriod.getDay() + 1, 0);
297        if (lastPeriod != null) {
298            lastPeriod.setNext(p);
299            p.setPrev(lastPeriod);
300        }
301        iPeriods.add(p);
302        return p;
303    }
304
305    /**
306     * Number of days
307     * @return number of days
308     */
309    public int getNrDays() {
310        return (iPeriods.get(iPeriods.size() - 1)).getDay() + 1;
311    }
312
313    /**
314     * Number of periods
315     * @return number of periods
316     */
317    public int getNrPeriods() {
318        return iPeriods.size();
319    }
320
321    /**
322     * List of periods, use
323     * {@link ExamModel#addPeriod(Long, String, String, int, int)} to add a
324     * period
325     * 
326     * @return list of {@link ExamPeriod}
327     */
328    public List<ExamPeriod> getPeriods() {
329        return iPeriods;
330    }
331
332    /** Period of given unique id 
333     * @param id period unique id
334     * @return the appropriate period
335     **/
336    public ExamPeriod getPeriod(Long id) {
337        for (ExamPeriod period : iPeriods) {
338            if (period.getId().equals(id))
339                return period;
340        }
341        return null;
342    }
343    
344    /**
345     * True when back-to-back student conflict is to be encountered when a
346     * student is enrolled into an exam that is on the last period of one day
347     * and another exam that is on the first period of the consecutive day. It
348     * can be set by problem property Exams.IsDayBreakBackToBack, or in the
349     * input xml file, property isDayBreakBackToBack)
350     * @return true if last exam on one day is back-to-back to the first exam of the following day
351     * 
352     */
353    public boolean isDayBreakBackToBack() {
354        return ((StudentBackToBackConflicts)getCriterion(StudentBackToBackConflicts.class)).isDayBreakBackToBack();
355    }
356    
357    /**
358     * Back-to-back distance, can be set by
359     * problem property Exams.BackToBackDistance, or in the input xml file,
360     * property backToBackDistance)
361     * @return back-to-back distance in meters
362     */
363    public double getBackToBackDistance() {
364        return ((StudentDistanceBackToBackConflicts)getCriterion(StudentDistanceBackToBackConflicts.class)).getBackToBackDistance();
365    }
366
367    /**
368     * Objective function.
369     * @return weighted sum of objective criteria
370     */
371    @Override
372    public double getTotalValue(Assignment<Exam, ExamPlacement> assignment) {
373        double total = 0;
374        for (Criterion<Exam, ExamPlacement> criterion: getCriteria())
375            total += criterion.getWeightedValue(assignment);
376        return total;
377    }
378
379    /**
380     * Return weighted individual objective criteria.
381     * @param assignment current assignment
382     * @return an array of weighted objective criteria
383     */
384    public double[] getTotalMultiValue(Assignment<Exam, ExamPlacement> assignment) {
385        double[] total = new double[getCriteria().size()];
386        int i = 0;
387        for (Criterion<Exam, ExamPlacement> criterion: getCriteria())
388            total[i++] = criterion.getWeightedValue(assignment);
389        return total;
390    }
391
392    /**
393     * String representation -- returns a list of values of objective criteria
394     * @param assignment current assignment
395     * @return comma separated list of {@link ExamCriterion#toString(Assignment)}
396     */
397    @Override
398    public String toString(Assignment<Exam, ExamPlacement> assignment) {
399        Set<String> props = new TreeSet<String>();
400        for (Criterion<Exam, ExamPlacement> criterion: getCriteria()) {
401            String val = ((ExamCriterion)criterion).toString(assignment);
402            if (!val.isEmpty())
403                props.add(val);
404        }
405        return props.toString();
406    }
407
408    /**
409     * Extended info table
410     */
411    @Override
412    public Map<String, String> getExtendedInfo(Assignment<Exam, ExamPlacement> assignment) {
413        Map<String, String> info = super.getExtendedInfo(assignment);
414        /*
415        info.put("Direct Conflicts [p]", String.valueOf(getNrDirectConflicts(true)));
416        info.put("More Than 2 A Day Conflicts [p]", String.valueOf(getNrMoreThanTwoADayConflicts(true)));
417        info.put("Back-To-Back Conflicts [p]", String.valueOf(getNrBackToBackConflicts(true)));
418        info.put("Distance Back-To-Back Conflicts [p]", String.valueOf(getNrDistanceBackToBackConflicts(true)));
419        info.put("Instructor Direct Conflicts [p]", String.valueOf(getNrInstructorDirectConflicts(true)));
420        info.put("Instructor More Than 2 A Day Conflicts [p]", String.valueOf(getNrInstructorMoreThanTwoADayConflicts(true)));
421        info.put("Instructor Back-To-Back Conflicts [p]", String.valueOf(getNrInstructorBackToBackConflicts(true)));
422        info.put("Instructor Distance Back-To-Back Conflicts [p]", String.valueOf(getNrInstructorDistanceBackToBackConflicts(true)));
423        info.put("Room Size Penalty [p]", String.valueOf(getRoomSizePenalty(true)));
424        info.put("Room Split Penalty [p]", String.valueOf(getRoomSplitPenalty(true)));
425        info.put("Period Penalty [p]", String.valueOf(getPeriodPenalty(true)));
426        info.put("Period Size Penalty [p]", String.valueOf(getPeriodSizePenalty(true)));
427        info.put("Period Index Penalty [p]", String.valueOf(getPeriodIndexPenalty(true)));
428        info.put("Room Penalty [p]", String.valueOf(getRoomPenalty(true)));
429        info.put("Distribution Penalty [p]", String.valueOf(getDistributionPenalty(true)));
430        info.put("Perturbation Penalty [p]", String.valueOf(getPerturbationPenalty(true)));
431        info.put("Room Perturbation Penalty [p]", String.valueOf(getRoomPerturbationPenalty(true)));
432        info.put("Room Split Distance Penalty [p]", sDoubleFormat.format(getRoomSplitDistancePenalty(true)) + " / " + getNrRoomSplits(true));
433        */
434        info.put("Number of Periods", String.valueOf(getPeriods().size()));
435        info.put("Number of Exams", String.valueOf(variables().size()));
436        info.put("Number of Rooms", String.valueOf(getRooms().size()));
437        info.put("Number of Students", String.valueOf(getStudents().size()));
438        int nrStudentExams = 0;
439        for (ExamStudent student : getStudents()) {
440            nrStudentExams += student.getOwners().size();
441        }
442        info.put("Number of Student Exams", String.valueOf(nrStudentExams));
443        int nrAltExams = 0, nrSmallExams = 0;
444        for (Exam exam : variables()) {
445            if (exam.hasAltSeating())
446                nrAltExams++;
447            if (exam.getMaxRooms() == 0)
448                nrSmallExams++;
449        }
450        info.put("Number of Exams Requiring Alt Seating", String.valueOf(nrAltExams));
451        info.put("Number of Small Exams (Exams W/O Room)", String.valueOf(nrSmallExams));
452        int[] nbrMtgs = new int[11];
453        for (int i = 0; i <= 10; i++)
454            nbrMtgs[i] = 0;
455        for (ExamStudent student : getStudents()) {
456            nbrMtgs[Math.min(10, student.variables().size())]++;
457        }
458        for (int i = 0; i <= 10; i++) {
459            if (nbrMtgs[i] == 0)
460                continue;
461            info.put("Number of Students with " + (i == 0 ? "no" : String.valueOf(i)) + (i == 10 ? " or more" : "")
462                    + " meeting" + (i != 1 ? "s" : ""), String.valueOf(nbrMtgs[i]));
463        }
464        Map<Integer, Integer> penalty2count = new HashMap<Integer, Integer>();
465        for (Exam exam: variables()) {
466                ExamPlacement placement = assignment.getValue(exam);
467                if (placement == null) continue;
468                Integer preference = placement.getPeriodPlacement().getExamPenalty();
469                Integer count = penalty2count.get(preference);
470                penalty2count.put(preference, 1 + (count == null ? 0 : count.intValue())); 
471        }
472        if (!penalty2count.isEmpty()) {
473            String value = null;
474            for (Integer penalty: new TreeSet<Integer>(penalty2count.keySet())) {
475                if (penalty == 0) continue;
476                value = (value == null ? "" : value + ", ") + penalty2count.get(penalty) +  "&times; " + penalty;
477            }
478            if (value != null)
479                info.put("Period Preferences", value);
480        }
481        return info;
482    }
483
484    /**
485     * Problem properties
486     * @return solver configuration
487     */
488    public DataProperties getProperties() {
489        return iProperties;
490    }
491
492    /**
493     * Problem rooms
494     * 
495     * @return list of {@link ExamRoom}
496     */
497    public List<ExamRoom> getRooms() {
498        return iRooms;
499    }
500
501    /**
502     * Problem students
503     * 
504     * @return list of {@link ExamStudent}
505     */
506    public List<ExamStudent> getStudents() {
507        return iStudents;
508    }
509
510    /**
511     * Problem instructors
512     * 
513     * @return list of {@link ExamInstructor}
514     */
515    public List<ExamInstructor> getInstructors() {
516        return iInstructors;
517    }
518
519    /**
520     * Distribution constraints
521     * 
522     * @return list of {@link ExamDistributionConstraint}
523     */
524    public List<ExamDistributionConstraint> getDistributionConstraints() {
525        return iDistributionConstraints;
526    }
527
528    private String getId(boolean anonymize, String type, String id) {
529        return (anonymize ? IdConvertor.getInstance().convert(type, id) : id);
530    }
531
532    /**
533     * Save model (including its solution) into XML.
534     * @param assignment current assignment
535     * @return created XML document
536     */
537    public Document save(Assignment<Exam, ExamPlacement> assignment) {
538        boolean saveInitial = getProperties().getPropertyBoolean("Xml.SaveInitial", true);
539        boolean saveBest = getProperties().getPropertyBoolean("Xml.SaveBest", true);
540        boolean saveSolution = getProperties().getPropertyBoolean("Xml.SaveSolution", true);
541        boolean saveConflictTable = getProperties().getPropertyBoolean("Xml.SaveConflictTable", false);
542        boolean saveParams = getProperties().getPropertyBoolean("Xml.SaveParameters", true);
543        boolean anonymize = getProperties().getPropertyBoolean("Xml.Anonymize", false);
544        boolean idconv = getProperties().getPropertyBoolean("Xml.ConvertIds", anonymize);
545        Document document = DocumentHelper.createDocument();
546        document.addComment("Examination Timetable");
547        if (assignment != null && assignment.nrAssignedVariables() > 0) {
548            StringBuffer comments = new StringBuffer("Solution Info:\n");
549            Map<String, String> solutionInfo = (getProperties().getPropertyBoolean("Xml.ExtendedInfo", false) ? getExtendedInfo(assignment) : getInfo(assignment));
550            for (String key : new TreeSet<String>(solutionInfo.keySet())) {
551                String value = solutionInfo.get(key);
552                comments.append("    " + key + ": " + value + "\n");
553            }
554            document.addComment(comments.toString());
555        }
556        Element root = document.addElement("examtt");
557        root.addAttribute("version", "1.0");
558        root.addAttribute("campus", getProperties().getProperty("Data.Initiative"));
559        root.addAttribute("term", getProperties().getProperty("Data.Term"));
560        root.addAttribute("year", getProperties().getProperty("Data.Year"));
561        root.addAttribute("created", String.valueOf(new Date()));
562        if (saveParams) {
563            Map<String, String> params = new HashMap<String, String>();
564            for (Criterion<Exam, ExamPlacement> criterion: getCriteria()) {
565                if (criterion instanceof ExamCriterion)
566                    ((ExamCriterion)criterion).getXmlParameters(params);
567            }
568            params.put("maxRooms", String.valueOf(getMaxRooms()));
569            params.put("checkForPeriodOverlaps", isCheckForPeriodOverlaps() ? "true" : "false");
570            Element parameters = root.addElement("parameters");
571            for (String key: new TreeSet<String>(params.keySet())) {
572                parameters.addElement("property").addAttribute("name", key).addAttribute("value", params.get(key));
573            }
574        }
575        Element periods = root.addElement("periods");
576        for (ExamPeriod period : getPeriods()) {
577            Element periodEl = periods.addElement("period").addAttribute("id", getId(idconv, "period", String.valueOf(period.getId())))
578                    .addAttribute("length", String.valueOf(period.getLength())).addAttribute("day", period.getDayStr())
579                    .addAttribute("time", period.getTimeStr()).addAttribute("penalty",
580                            String.valueOf(period.getPenalty()));
581            if (period.getStartTime() != null)
582                periodEl.addAttribute("start", period.getStartTime().toString());
583        }
584        Element rooms = root.addElement("rooms");
585        for (ExamRoom room : getRooms()) {
586            Element r = rooms.addElement("room");
587            r.addAttribute("id", getId(idconv, "room", String.valueOf(room.getId())));
588            if (!anonymize && room.hasName())
589                r.addAttribute("name", room.getName());
590            r.addAttribute("size", String.valueOf(room.getSize()));
591            r.addAttribute("alt", String.valueOf(room.getAltSize()));
592            if (!room.isHard())
593                r.addAttribute("hard", "false");
594            if (room.getCoordX() != null && room.getCoordY() != null)
595                r.addAttribute("coordinates", room.getCoordX() + "," + room.getCoordY());
596            if (room.getParentRoom() != null)
597                r.addAttribute("parentId", getId(idconv, "room", String.valueOf(room.getParentRoom().getId())));
598            for (ExamPeriod period : getPeriods()) {
599                if (!room.isAvailable(period))
600                    r.addElement("period").addAttribute("id",
601                            getId(idconv, "period", String.valueOf(period.getId()))).addAttribute("available",
602                            "false");
603                else if (room.getPenalty(period) != 0)
604                    r.addElement("period").addAttribute("id",
605                            getId(idconv, "period", String.valueOf(period.getId()))).addAttribute("penalty",
606                            String.valueOf(room.getPenalty(period)));
607            }
608            Map<Long, Integer> travelTimes = getDistanceMetric().getTravelTimes().get(room.getId());
609            if (travelTimes != null)
610                for (Map.Entry<Long, Integer> time: travelTimes.entrySet())
611                    r.addElement("travel-time").addAttribute("id", getId(idconv, "room", time.getKey().toString())).addAttribute("minutes", time.getValue().toString());
612        }
613        Element exams = root.addElement("exams");
614        for (Exam exam : variables()) {
615            Element ex = exams.addElement("exam");
616            ex.addAttribute("id", getId(idconv, "exam", String.valueOf(exam.getId())));
617            if (!anonymize && exam.hasName())
618                ex.addAttribute("name", exam.getName());
619            ex.addAttribute("length", String.valueOf(exam.getLength()));
620            if (exam.getSizeOverride() != null)
621                ex.addAttribute("size", exam.getSizeOverride().toString());
622            if (exam.getMinSize() != 0)
623                ex.addAttribute("minSize", String.valueOf(exam.getMinSize()));
624            ex.addAttribute("alt", (exam.hasAltSeating() ? "true" : "false"));
625            if (exam.getMaxRooms() != getMaxRooms())
626                ex.addAttribute("maxRooms", String.valueOf(exam.getMaxRooms()));
627            if (exam.getPrintOffset() != null && !anonymize)
628                ex.addAttribute("printOffset", exam.getPrintOffset().toString());
629            if (!anonymize)
630                ex.addAttribute("enrl", String.valueOf(exam.getStudents().size()));
631            if (!anonymize)
632                for (ExamOwner owner : exam.getOwners()) {
633                    Element o = ex.addElement("owner");
634                    o.addAttribute("id", getId(idconv, "owner", String.valueOf(owner.getId())));
635                    o.addAttribute("name", owner.getName());
636                }
637            for (ExamPeriodPlacement period : exam.getPeriodPlacements()) {
638                Element pe = ex.addElement("period").addAttribute("id",
639                        getId(idconv, "period", String.valueOf(period.getId())));
640                int penalty = period.getExamPenalty();
641                if (penalty != 0)
642                    pe.addAttribute("penalty", String.valueOf(penalty));
643            }
644            for (ExamRoomPlacement room : exam.getRoomPlacements()) {
645                Element re = ex.addElement("room").addAttribute("id",
646                        getId(idconv, "room", String.valueOf(room.getId())));
647                if (room.getPenalty() != 0)
648                    re.addAttribute("penalty", String.valueOf(room.getPenalty()));
649                if (room.getMaxPenalty() != 100)
650                    re.addAttribute("maxPenalty", String.valueOf(room.getMaxPenalty()));
651            }
652            if (exam.hasAveragePeriod())
653                ex.addAttribute("average", String.valueOf(exam.getAveragePeriod()));
654            ExamPlacement p = (assignment == null ? null : assignment.getValue(exam));
655            if (p != null && saveSolution) {
656                Element asg = ex.addElement("assignment");
657                asg.addElement("period").addAttribute("id",
658                        getId(idconv, "period", String.valueOf(p.getPeriod().getId())));
659                for (ExamRoomPlacement r : p.getRoomPlacements()) {
660                    asg.addElement("room").addAttribute("id", getId(idconv, "room", String.valueOf(r.getId())));
661                }
662            }
663            p = exam.getInitialAssignment();
664            if (p != null && saveInitial) {
665                Element ini = ex.addElement("initial");
666                ini.addElement("period").addAttribute("id",
667                        getId(idconv, "period", String.valueOf(p.getPeriod().getId())));
668                for (ExamRoomPlacement r : p.getRoomPlacements()) {
669                    ini.addElement("room").addAttribute("id", getId(idconv, "room", String.valueOf(r.getId())));
670                }
671            }
672            p = exam.getBestAssignment();
673            if (p != null && saveBest) {
674                Element ini = ex.addElement("best");
675                ini.addElement("period").addAttribute("id",
676                        getId(idconv, "period", String.valueOf(p.getPeriod().getId())));
677                for (ExamRoomPlacement r : p.getRoomPlacements()) {
678                    ini.addElement("room").addAttribute("id", getId(idconv, "room", String.valueOf(r.getId())));
679                }
680            }
681            if (iRoomSharing != null)
682                iRoomSharing.save(exam, ex, anonymize ? IdConvertor.getInstance() : null);
683        }
684        Element students = root.addElement("students");
685        for (ExamStudent student : getStudents()) {
686            Element s = students.addElement("student");
687            s.addAttribute("id", getId(idconv, "student", String.valueOf(student.getId())));
688            for (Exam ex : student.variables()) {
689                Element x = s.addElement("exam").addAttribute("id",
690                        getId(idconv, "exam", String.valueOf(ex.getId())));
691                if (!anonymize)
692                    for (ExamOwner owner : ex.getOwners(student)) {
693                        x.addElement("owner").addAttribute("id",
694                                getId(idconv, "owner", String.valueOf(owner.getId())));
695                    }
696            }
697            for (ExamPeriod period : getPeriods()) {
698                if (!student.isAvailable(period))
699                    s.addElement("period").addAttribute("id",
700                            getId(idconv, "period", String.valueOf(period.getId()))).addAttribute("available",
701                            "false");
702            }
703        }
704        Element instructors = root.addElement("instructors");
705        for (ExamInstructor instructor : getInstructors()) {
706            Element i = instructors.addElement("instructor");
707            i.addAttribute("id", getId(idconv, "instructor", String.valueOf(instructor.getId())));
708            if (!anonymize && instructor.hasName())
709                i.addAttribute("name", instructor.getName());
710            for (Exam ex : instructor.variables()) {
711                Element x = i.addElement("exam").addAttribute("id",
712                        getId(idconv, "exam", String.valueOf(ex.getId())));
713                if (!anonymize)
714                    for (ExamOwner owner : ex.getOwners(instructor)) {
715                        x.addElement("owner").addAttribute("id",
716                                getId(idconv, "owner", String.valueOf(owner.getId())));
717                    }
718            }
719            for (ExamPeriod period : getPeriods()) {
720                if (!instructor.isAvailable(period))
721                    i.addElement("period").addAttribute("id",
722                            getId(idconv, "period", String.valueOf(period.getId()))).addAttribute("available",
723                            "false");
724            }
725        }
726        Element distConstraints = root.addElement("constraints");
727        for (ExamDistributionConstraint distConstraint : getDistributionConstraints()) {
728            Element dc = distConstraints.addElement(distConstraint.getTypeString());
729            dc.addAttribute("id", getId(idconv, "constraint", String.valueOf(distConstraint.getId())));
730            if (!distConstraint.isHard()) {
731                dc.addAttribute("hard", "false");
732                dc.addAttribute("weight", String.valueOf(distConstraint.getWeight()));
733            }
734            for (Exam exam : distConstraint.variables()) {
735                dc.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(exam.getId())));
736            }
737        }
738        if (saveConflictTable && assignment != null) {
739            Element conflicts = root.addElement("conflicts");
740            Map<ExamStudent, Set<Exam>> studentsOfPreviousPeriod = null;
741            for (ExamPeriod period : getPeriods()) {
742                Map<ExamStudent, Set<Exam>> studentsOfPeriod = getStudentsOfPeriod(assignment, period);
743                for (Map.Entry<ExamStudent, Set<Exam>> entry : studentsOfPeriod.entrySet()) {
744                    ExamStudent student = entry.getKey();
745                    Set<Exam> examsOfStudent = entry.getValue();
746                    if (examsOfStudent.size() > 1) {
747                        Element dir = conflicts.addElement("direct").addAttribute("student", getId(idconv, "student", String.valueOf(student.getId())));
748                        for (Exam exam : examsOfStudent) {
749                            dir.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(exam.getId())));
750                        }
751                    }
752                    if (examsOfStudent.size() > 0 && studentsOfPreviousPeriod != null && (isDayBreakBackToBack() || period.prev().getDay() == period.getDay())) {
753                        Set<Exam> previousExamsOfStudent = studentsOfPreviousPeriod.get(student);
754                        if (previousExamsOfStudent != null) {
755                            for (Exam ex1 : previousExamsOfStudent)
756                                for (Exam ex2 : examsOfStudent) {
757                                    Element btb = conflicts.addElement("back-to-back").addAttribute("student", getId(idconv, "student", String.valueOf(student.getId())));
758                                    btb.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(ex1.getId())));
759                                    btb.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(ex2.getId())));
760                                    if (getBackToBackDistance() >= 0 && period.prev().getDay() == period.getDay()) {
761                                        double dist = (assignment.getValue(ex1)).getDistanceInMeters(assignment.getValue(ex2));
762                                        if (dist > 0)
763                                            btb.addAttribute("distance", String.valueOf(dist));
764                                    }
765                                }
766                        }
767                    }
768                }
769                if (period.next() == null || period.next().getDay() != period.getDay()) {
770                    Map<ExamStudent, Set<Exam>> studentsOfDay = getStudentsOfDay(assignment, period);
771                    for (Map.Entry<ExamStudent, Set<Exam>> entry : studentsOfDay.entrySet()) {
772                        ExamStudent student = entry.getKey();
773                        Set<Exam> examsOfStudent = entry.getValue();
774                        if (examsOfStudent.size() > 2) {
775                            Element mt2 = conflicts.addElement("more-2-day").addAttribute("student", getId(idconv, "student", String.valueOf(student.getId())));
776                            for (Exam exam : examsOfStudent) {
777                                mt2.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(exam.getId())));
778                            }
779                        }
780                    }
781                }
782                studentsOfPreviousPeriod = studentsOfPeriod;
783            }
784            /*
785            Element conflicts = root.addElement("conflicts");
786            for (ExamStudent student : getStudents()) {
787                for (ExamPeriod period : getPeriods()) {
788                    int nrExams = student.getExams(assignment, period).size();
789                    if (nrExams > 1) {
790                        Element dir = conflicts.addElement("direct").addAttribute("student", getId(idconv, "student", String.valueOf(student.getId())));
791                        for (Exam exam : student.getExams(assignment, period)) {
792                            dir.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(exam.getId())));
793                        }
794                    }
795                    if (nrExams > 0) {
796                        if (period.next() != null && !student.getExams(assignment, period.next()).isEmpty()
797                                && (!isDayBreakBackToBack() || period.next().getDay() == period.getDay())) {
798                            for (Exam ex1 : student.getExams(assignment, period)) {
799                                for (Exam ex2 : student.getExams(assignment, period.next())) {
800                                    Element btb = conflicts.addElement("back-to-back").addAttribute("student", getId(idconv, "student", String.valueOf(student.getId())));
801                                    btb.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(ex1.getId())));
802                                    btb.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(ex2.getId())));
803                                    if (getBackToBackDistance() >= 0) {
804                                        double dist = (assignment.getValue(ex1)).getDistanceInMeters(assignment.getValue(ex2));
805                                        if (dist > 0)
806                                            btb.addAttribute("distance", String.valueOf(dist));
807                                    }
808                                }
809                            }
810                        }
811                    }
812                    if (period.next() == null || period.next().getDay() != period.getDay()) {
813                        int nrExamsADay = student.getExamsADay(assignment, period.getDay()).size();
814                        if (nrExamsADay > 2) {
815                            Element mt2 = conflicts.addElement("more-2-day").addAttribute("student", getId(idconv, "student", String.valueOf(student.getId())));
816                            for (Exam exam : student.getExamsADay(assignment, period.getDay())) {
817                                mt2.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(exam.getId())));
818                            }
819                        }
820                    }
821                }
822            }
823            */
824        }
825        return document;
826    }
827
828    /**
829     * Load model (including its solution) from XML.
830     * @param document XML document
831     * @param assignment assignment to be loaded
832     * @return true if successfully loaded
833     */
834    public boolean load(Document document, Assignment<Exam, ExamPlacement> assignment) {
835        return load(document, assignment, null);
836    }
837
838    /**
839     * Load model (including its solution) from XML.
840     * @param document XML document
841     * @param assignment assignment to be loaded
842     * @param saveBest callback executed once the best assignment is loaded and assigned
843     * @return true if successfully loaded
844     */
845    public boolean load(Document document, Assignment<Exam, ExamPlacement> assignment, Callback saveBest) {
846        boolean loadInitial = getProperties().getPropertyBoolean("Xml.LoadInitial", true);
847        boolean loadBest = getProperties().getPropertyBoolean("Xml.LoadBest", true);
848        boolean loadSolution = getProperties().getPropertyBoolean("Xml.LoadSolution", true);
849        boolean loadParams = getProperties().getPropertyBoolean("Xml.LoadParameters", false);
850        Integer softPeriods = getProperties().getPropertyInteger("Exam.SoftPeriods", null);
851        Integer softRooms = getProperties().getPropertyInteger("Exam.SoftRooms", null);
852        Integer softDistributions = getProperties().getPropertyInteger("Exam.SoftDistributions", null);
853        Element root = document.getRootElement();
854        if (!"examtt".equals(root.getName()))
855            return false;
856        if (root.attribute("campus") != null)
857            getProperties().setProperty("Data.Campus", root.attributeValue("campus"));
858        else if (root.attribute("initiative") != null)
859            getProperties().setProperty("Data.Initiative", root.attributeValue("initiative"));
860        if (root.attribute("term") != null)
861            getProperties().setProperty("Data.Term", root.attributeValue("term"));
862        if (root.attribute("year") != null)
863            getProperties().setProperty("Data.Year", root.attributeValue("year"));
864        if (loadParams && root.element("parameters") != null) {
865            Map<String,String> params = new HashMap<String, String>();
866            for (Iterator<?> i = root.element("parameters").elementIterator("property"); i.hasNext();) {
867                Element e = (Element) i.next();
868                params.put(e.attributeValue("name"), e.attributeValue("value"));
869            }
870            for (Criterion<Exam, ExamPlacement> criterion: getCriteria()) {
871                if (criterion instanceof ExamCriterion)
872                    ((ExamCriterion)criterion).setXmlParameters(params);
873            }
874            try {
875                setMaxRooms(Integer.valueOf(params.get("maxRooms")));
876            } catch (NumberFormatException e) {} catch (NullPointerException e) {}
877            setCheckForPeriodOverlaps("true".equalsIgnoreCase(params.get("checkForPeriodOverlaps")));
878        }
879        for (Iterator<?> i = root.element("periods").elementIterator("period"); i.hasNext();) {
880            Element e = (Element) i.next();
881            ExamPeriod p = addPeriod(Long.valueOf(e.attributeValue("id")), e.attributeValue("day"), e.attributeValue("time"), Integer
882                    .parseInt(e.attributeValue("length")), Integer.parseInt(e.attributeValue("penalty") == null ? e
883                    .attributeValue("weight", "0") : e.attributeValue("penalty")));
884            if (e.attributeValue("start") != null)
885                p.setStartTime(Integer.valueOf(e.attributeValue("start")));
886        }
887        HashMap<Long, ExamRoom> rooms = new HashMap<Long, ExamRoom>();
888        HashMap<String, ArrayList<ExamRoom>> roomGroups = new HashMap<String, ArrayList<ExamRoom>>();
889        HashMap<ExamRoom, Long> roomPartitions = new HashMap<ExamRoom, Long>();
890        for (Iterator<?> i = root.element("rooms").elementIterator("room"); i.hasNext();) {
891            Element e = (Element) i.next();
892            String coords = e.attributeValue("coordinates");
893            ExamRoom room = new ExamRoom(this, Long.parseLong(e.attributeValue("id")), e.attributeValue("name"),
894                    Integer.parseInt(e.attributeValue("size")), Integer.parseInt(e.attributeValue("alt")),
895                    (coords == null ? null : Double.valueOf(coords.substring(0, coords.indexOf(',')))),
896                    (coords == null ? null : Double.valueOf(coords.substring(coords.indexOf(',') + 1))));
897            room.setHard("true".equalsIgnoreCase(e.attributeValue("hard", "true")));
898            addConstraint(room);
899            getRooms().add(room);
900            rooms.put(Long.valueOf(room.getId()), room);
901            for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) {
902                Element pe = (Element) j.next();
903                ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id")));
904                if (period == null) continue;
905                if ("false".equals(pe.attributeValue("available"))) {
906                    if (softRooms == null)
907                        room.setAvailable(period, false);
908                    else
909                        room.setPenalty(period, softRooms);
910                } else
911                    room.setPenalty(period, Integer.parseInt(pe.attributeValue("penalty")));
912            }
913            String av = e.attributeValue("available");
914            if (av != null) {
915                for (int j = 0; j < getPeriods().size(); j++)
916                    if ('0' == av.charAt(j))
917                        room.setAvailable(getPeriods().get(j), false);
918            }
919            String g = e.attributeValue("groups");
920            if (g != null) {
921                for (StringTokenizer s = new StringTokenizer(g, ","); s.hasMoreTokens();) {
922                    String gr = s.nextToken();
923                    ArrayList<ExamRoom> roomsThisGrop = roomGroups.get(gr);
924                    if (roomsThisGrop == null) {
925                        roomsThisGrop = new ArrayList<ExamRoom>();
926                        roomGroups.put(gr, roomsThisGrop);
927                    }
928                    roomsThisGrop.add(room);
929                }
930            }
931            if (e.attributeValue("parentId") != null)
932                roomPartitions.put(room, Long.valueOf(e.attributeValue("parentId")));
933            for (Iterator<?> j = e.elementIterator("travel-time"); j.hasNext();) {
934                Element travelTimeEl = (Element)j.next();
935                getDistanceMetric().addTravelTime(room.getId(),
936                        Long.valueOf(travelTimeEl.attributeValue("id")),
937                        Integer.valueOf(travelTimeEl.attributeValue("minutes")));
938            }
939        }
940        for (Map.Entry<ExamRoom, Long> partition: roomPartitions.entrySet()) {
941            ExamRoom parent = rooms.get(partition.getValue());
942            if (parent != null)
943                parent.addPartition(partition.getKey());
944        }
945        ArrayList<ExamPlacement> assignments = new ArrayList<ExamPlacement>();
946        HashMap<Long, Exam> exams = new HashMap<Long, Exam>();
947        HashMap<Long, ExamOwner> courseSections = new HashMap<Long, ExamOwner>();
948        for (Iterator<?> i = root.element("exams").elementIterator("exam"); i.hasNext();) {
949            Element e = (Element) i.next();
950            ArrayList<ExamPeriodPlacement> periodPlacements = new ArrayList<ExamPeriodPlacement>();
951            if (softPeriods != null) {
952                for (ExamPeriod period: getPeriods()) {
953                    int penalty = softPeriods;
954                    for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) {
955                        Element pe = (Element) j.next();
956                        if (period.getId().equals(Long.valueOf(pe.attributeValue("id")))) {
957                            penalty = Integer.parseInt(pe.attributeValue("penalty", "0"));
958                            break;
959                        }
960                    }
961                    periodPlacements.add(new ExamPeriodPlacement(period, penalty));
962                }
963            } else {
964                for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) {
965                    Element pe = (Element) j.next();
966                    ExamPeriod p = getPeriod(Long.valueOf(pe.attributeValue("id")));
967                    if (p != null)
968                        periodPlacements.add(new ExamPeriodPlacement(p, Integer.parseInt(pe.attributeValue("penalty", "0"))));
969                }
970            }
971            ArrayList<ExamRoomPlacement> roomPlacements = new ArrayList<ExamRoomPlacement>();
972            if (softRooms != null) {
973                for (ExamRoom room: getRooms()) {
974                    boolean av = false;
975                    for (ExamPeriodPlacement p: periodPlacements) {
976                        if (room.isAvailable(p.getPeriod()) && room.getPenalty(p.getPeriod()) != softRooms) { av = true; break; }
977                    }
978                    if (!av) continue;
979                    int penalty = softRooms, maxPenalty = softRooms;
980                    for (Iterator<?> j = e.elementIterator("room"); j.hasNext();) {
981                        Element re = (Element) j.next();
982                        if (room.getId() == Long.parseLong(re.attributeValue("id"))) {
983                            penalty = Integer.parseInt(re.attributeValue("penalty", "0"));
984                            maxPenalty = Integer.parseInt(re.attributeValue("maxPenalty", softRooms.toString()));
985                        }
986                    }
987                    roomPlacements.add(new ExamRoomPlacement(room, penalty, maxPenalty));
988                }                
989            } else {
990                for (Iterator<?> j = e.elementIterator("room"); j.hasNext();) {
991                    Element re = (Element) j.next();
992                    ExamRoomPlacement room = new ExamRoomPlacement(rooms.get(Long.valueOf(re.attributeValue("id"))),
993                            Integer.parseInt(re.attributeValue("penalty", "0")),
994                            Integer.parseInt(re.attributeValue("maxPenalty", "100")));
995                    if (room.getRoom().isAvailable())
996                        roomPlacements.add(room);
997                }
998            }
999            String g = e.attributeValue("groups");
1000            if (g != null) {
1001                HashMap<ExamRoom, Integer> allRooms = new HashMap<ExamRoom, Integer>();
1002                for (StringTokenizer s = new StringTokenizer(g, ","); s.hasMoreTokens();) {
1003                    String gr = s.nextToken();
1004                    ArrayList<ExamRoom> roomsThisGrop = roomGroups.get(gr);
1005                    if (roomsThisGrop != null)
1006                        for (ExamRoom r : roomsThisGrop)
1007                            allRooms.put(r, 0);
1008                }
1009                for (Iterator<?> j = e.elementIterator("original-room"); j.hasNext();) {
1010                    allRooms.put((rooms.get(Long.valueOf(((Element) j.next()).attributeValue("id")))), Integer.valueOf(-1));
1011                }
1012                for (Map.Entry<ExamRoom, Integer> entry : allRooms.entrySet()) {
1013                    ExamRoomPlacement room = new ExamRoomPlacement(entry.getKey(), entry.getValue(), 100);
1014                    roomPlacements.add(room);
1015                }
1016                if (periodPlacements.isEmpty()) {
1017                    for (ExamPeriod p : getPeriods()) {
1018                        periodPlacements.add(new ExamPeriodPlacement(p, 0));
1019                    }
1020                }
1021            }
1022            Exam exam = new Exam(Long.parseLong(e.attributeValue("id")), e.attributeValue("name"), Integer.parseInt(e
1023                    .attributeValue("length")), "true".equals(e.attributeValue("alt")),
1024                    (e.attribute("maxRooms") == null ? getMaxRooms() : Integer.parseInt(e.attributeValue("maxRooms"))),
1025                    Integer.parseInt(e.attributeValue("minSize", "0")), periodPlacements, roomPlacements);
1026            if (e.attributeValue("size") != null)
1027                exam.setSizeOverride(Integer.valueOf(e.attributeValue("size")));
1028            if (e.attributeValue("printOffset") != null)
1029                exam.setPrintOffset(Integer.valueOf(e.attributeValue("printOffset")));
1030            exams.put(Long.valueOf(exam.getId()), exam);
1031            addVariable(exam);
1032            if (e.attribute("average") != null)
1033                exam.setAveragePeriod(Integer.parseInt(e.attributeValue("average")));
1034            Element asg = e.element("assignment");
1035            if (asg != null && loadSolution) {
1036                Element per = asg.element("period");
1037                if (per != null) {
1038                    HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>();
1039                    for (Iterator<?> j = asg.elementIterator("room"); j.hasNext();)
1040                        rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id"))));
1041                    ExamPeriodPlacement pp = exam.getPeriodPlacement(Long.valueOf(per.attributeValue("id")));
1042                    if (pp != null)
1043                        assignments.add(new ExamPlacement(exam, pp, rp));
1044                }
1045            }
1046            Element ini = e.element("initial");
1047            if (ini != null && loadInitial) {
1048                Element per = ini.element("period");
1049                if (per != null) {
1050                    HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>();
1051                    for (Iterator<?> j = ini.elementIterator("room"); j.hasNext();)
1052                        rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id"))));
1053                    ExamPeriodPlacement pp = exam.getPeriodPlacement(Long.valueOf(per.attributeValue("id")));
1054                    if (pp != null)
1055                        exam.setInitialAssignment(new ExamPlacement(exam, pp, rp));
1056                }
1057            }
1058            Element best = e.element("best");
1059            if (best != null && loadBest) {
1060                Element per = best.element("period");
1061                if (per != null) {
1062                    HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>();
1063                    for (Iterator<?> j = best.elementIterator("room"); j.hasNext();)
1064                        rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id"))));
1065                    ExamPeriodPlacement pp = exam.getPeriodPlacement(Long.valueOf(per.attributeValue("id")));
1066                    if (pp != null)
1067                        exam.setBestAssignment(new ExamPlacement(exam, pp, rp), 0);
1068                }
1069            }
1070            for (Iterator<?> j = e.elementIterator("owner"); j.hasNext();) {
1071                Element f = (Element) j.next();
1072                ExamOwner owner = new ExamOwner(exam, Long.parseLong(f.attributeValue("id")), f.attributeValue("name"));
1073                exam.getOwners().add(owner);
1074                courseSections.put(Long.valueOf(owner.getId()), owner);
1075            }
1076            if (iRoomSharing != null)
1077                iRoomSharing.load(exam, e);
1078        }
1079        for (Iterator<?> i = root.element("students").elementIterator("student"); i.hasNext();) {
1080            Element e = (Element) i.next();
1081            ExamStudent student = new ExamStudent(this, Long.parseLong(e.attributeValue("id")));
1082            for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) {
1083                Element x = (Element) j.next();
1084                Exam ex = exams.get(Long.valueOf(x.attributeValue("id")));
1085                student.addVariable(ex);
1086                for (Iterator<?> k = x.elementIterator("owner"); k.hasNext();) {
1087                    Element f = (Element) k.next();
1088                    ExamOwner owner = courseSections.get(Long.valueOf(f.attributeValue("id")));
1089                    student.getOwners().add(owner);
1090                    owner.getStudents().add(student);
1091                }
1092            }
1093            String available = e.attributeValue("available");
1094            if (available != null)
1095                for (ExamPeriod period : getPeriods()) {
1096                    if (available.charAt(period.getIndex()) == '0')
1097                        student.setAvailable(period.getIndex(), false);
1098                }
1099            for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) {
1100                Element pe = (Element) j.next();
1101                ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id")));
1102                if (period == null) continue;
1103                if ("false".equals(pe.attributeValue("available")))
1104                    student.setAvailable(period.getIndex(), false);
1105            }
1106            addConstraint(student);
1107            getStudents().add(student);
1108        }
1109        if (root.element("instructors") != null)
1110            for (Iterator<?> i = root.element("instructors").elementIterator("instructor"); i.hasNext();) {
1111                Element e = (Element) i.next();
1112                ExamInstructor instructor = new ExamInstructor(this, Long.parseLong(e.attributeValue("id")), e
1113                        .attributeValue("name"));
1114                for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) {
1115                    Element x = (Element) j.next();
1116                    Exam ex = exams.get(Long.valueOf(x.attributeValue("id")));
1117                    instructor.addVariable(ex);
1118                    for (Iterator<?> k = x.elementIterator("owner"); k.hasNext();) {
1119                        Element f = (Element) k.next();
1120                        ExamOwner owner = courseSections.get(Long.valueOf(f.attributeValue("id")));
1121                        instructor.getOwners().add(owner);
1122                        owner.getIntructors().add(instructor);
1123                    }
1124                }
1125                String available = e.attributeValue("available");
1126                if (available != null)
1127                    for (ExamPeriod period : getPeriods()) {
1128                        if (available.charAt(period.getIndex()) == '0')
1129                            instructor.setAvailable(period.getIndex(), false);
1130                    }
1131                for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) {
1132                    Element pe = (Element) j.next();
1133                    ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id")));
1134                    if (period == null) continue;
1135                    if ("false".equals(pe.attributeValue("available")))
1136                        instructor.setAvailable(period.getIndex(), false);
1137                }
1138                addConstraint(instructor);
1139                getInstructors().add(instructor);
1140            }
1141        if (root.element("constraints") != null)
1142            for (Iterator<?> i = root.element("constraints").elementIterator(); i.hasNext();) {
1143                Element e = (Element) i.next();
1144                ExamDistributionConstraint dc = new ExamDistributionConstraint(Long.parseLong(e.attributeValue("id")),
1145                        e.getName(),
1146                        softDistributions != null ? false : "true".equals(e.attributeValue("hard", "true")),
1147                        (softDistributions != null && "true".equals(e.attributeValue("hard", "true")) ? softDistributions : Integer.parseInt(e.attributeValue("weight", "0"))));
1148                for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) {
1149                    dc.addVariable(exams.get(Long.valueOf(((Element) j.next()).attributeValue("id"))));
1150                }
1151                addConstraint(dc);
1152                getDistributionConstraints().add(dc);
1153            }
1154        init();
1155        if (loadBest && saveBest != null && assignment != null) {
1156            for (Exam exam : variables()) {
1157                ExamPlacement placement = exam.getBestAssignment();
1158                if (placement == null)
1159                    continue;
1160                assignment.assign(0, placement);
1161            }
1162            saveBest.execute();
1163            for (Exam exam : variables()) {
1164                if (assignment.getValue(exam) != null)
1165                    assignment.unassign(0, exam);
1166            }
1167        }
1168        if (assignment != null) {
1169            for (ExamPlacement placement : assignments) {
1170                Exam exam = placement.variable();
1171                Set<ExamPlacement> conf = conflictValues(assignment, placement);
1172                if (!conf.isEmpty()) {
1173                    for (Map.Entry<Constraint<Exam, ExamPlacement>, Set<ExamPlacement>> entry : conflictConstraints(assignment, placement).entrySet()) {
1174                        Constraint<Exam, ExamPlacement> constraint = entry.getKey();
1175                        Set<ExamPlacement> values = entry.getValue();
1176                        if (constraint instanceof ExamStudent) {
1177                            ((ExamStudent) constraint).setAllowDirectConflicts(true);
1178                            exam.setAllowDirectConflicts(true);
1179                            for (ExamPlacement p : values)
1180                                p.variable().setAllowDirectConflicts(true);
1181                        }
1182                    }
1183                    conf = conflictValues(assignment, placement);
1184                }
1185                if (conf.isEmpty()) {
1186                    assignment.assign(0, placement);
1187                } else {
1188                    sLog.error("Unable to assign " + exam.getInitialAssignment().getName() + " to exam " + exam.getName());
1189                    sLog.error("Conflicts:" + ToolBox.dict2string(conflictConstraints(assignment, exam.getInitialAssignment()), 2));
1190                }
1191            }
1192        }
1193        return true;
1194    }
1195
1196    @Override
1197    public ExamContext createAssignmentContext(Assignment<Exam, ExamPlacement> assignment) {
1198        return new ExamContext(this, assignment);
1199    }
1200    
1201    public Map<ExamStudent, Set<Exam>> getStudentsOfPeriod(Assignment<Exam, ExamPlacement> assignment, ExamPeriod period) {
1202        return getContext(assignment).getStudentsOfPeriod(period.getIndex());
1203    }
1204    
1205    public Map<ExamStudent, Set<Exam>> getStudentsOfDay(Assignment<Exam, ExamPlacement> assignment, ExamPeriod period) {
1206        return getContext(assignment).getStudentsOfDay(period.getDay());
1207    }
1208    
1209    public Map<ExamStudent, Set<Exam>> getStudentsOfDay(Assignment<Exam, ExamPlacement> assignment, int day) {
1210        return getContext(assignment).getStudentsOfDay(day);
1211    }
1212    
1213    public Map<ExamInstructor, Set<Exam>> getInstructorsOfPeriod(Assignment<Exam, ExamPlacement> assignment, ExamPeriod period) {
1214        return getContext(assignment).getInstructorsOfPeriod(period.getIndex());
1215    }
1216    
1217    public Map<ExamInstructor, Set<Exam>> getInstructorsOfDay(Assignment<Exam, ExamPlacement> assignment, ExamPeriod period) {
1218        return getContext(assignment).getInstructorsOfDay(period.getDay());
1219    }
1220    
1221    public Map<ExamInstructor, Set<Exam>> getInstructorsOfDay(Assignment<Exam, ExamPlacement> assignment, int day) {
1222        return getContext(assignment).getInstructorsOfDay(day);
1223    }
1224
1225}