001package org.cpsolver.coursett;
002
003import java.io.File;
004import java.io.FileOutputStream;
005import java.io.IOException;
006import java.text.DecimalFormat;
007import java.text.DecimalFormatSymbols;
008import java.util.ArrayList;
009import java.util.BitSet;
010import java.util.Collections;
011import java.util.Date;
012import java.util.HashSet;
013import java.util.HashMap;
014import java.util.Iterator;
015import java.util.List;
016import java.util.Locale;
017import java.util.Map;
018import java.util.Set;
019import java.util.TreeSet;
020
021
022import org.cpsolver.coursett.constraint.ClassLimitConstraint;
023import org.cpsolver.coursett.constraint.DiscouragedRoomConstraint;
024import org.cpsolver.coursett.constraint.FlexibleConstraint;
025import org.cpsolver.coursett.constraint.GroupConstraint;
026import org.cpsolver.coursett.constraint.IgnoreStudentConflictsConstraint;
027import org.cpsolver.coursett.constraint.InstructorConstraint;
028import org.cpsolver.coursett.constraint.MinimizeNumberOfUsedGroupsOfTime;
029import org.cpsolver.coursett.constraint.MinimizeNumberOfUsedRoomsConstraint;
030import org.cpsolver.coursett.constraint.RoomConstraint;
031import org.cpsolver.coursett.constraint.SpreadConstraint;
032import org.cpsolver.coursett.model.Configuration;
033import org.cpsolver.coursett.model.Lecture;
034import org.cpsolver.coursett.model.Placement;
035import org.cpsolver.coursett.model.RoomLocation;
036import org.cpsolver.coursett.model.RoomSharingModel;
037import org.cpsolver.coursett.model.Student;
038import org.cpsolver.coursett.model.TimeLocation;
039import org.cpsolver.ifs.model.Constraint;
040import org.cpsolver.ifs.solver.Solver;
041import org.cpsolver.ifs.util.Progress;
042import org.cpsolver.ifs.util.ToolBox;
043import org.dom4j.Document;
044import org.dom4j.DocumentHelper;
045import org.dom4j.Element;
046import org.dom4j.io.OutputFormat;
047import org.dom4j.io.XMLWriter;
048
049/**
050 * This class saves the resultant solution in the XML format. <br>
051 * <br>
052 * Parameters:
053 * <table border='1' summary='Related Solver Parameters'>
054 * <tr>
055 * <th>Parameter</th>
056 * <th>Type</th>
057 * <th>Comment</th>
058 * </tr>
059 * <tr>
060 * <td>General.Output</td>
061 * <td>{@link String}</td>
062 * <td>Folder with the output solution in XML format (solution.xml)</td>
063 * </tr>
064 * <tr>
065 * <td>Xml.ConvertIds</td>
066 * <td>{@link Boolean}</td>
067 * <td>If true, ids are converted (to be able to make input data public)</td>
068 * </tr>
069 * <tr>
070 * <td>Xml.ShowNames</td>
071 * <td>{@link Boolean}</td>
072 * <td>If false, names are not exported (to be able to make input data public)</td>
073 * </tr>
074 * <tr>
075 * <td>Xml.SaveBest</td>
076 * <td>{@link Boolean}</td>
077 * <td>If true, best solution is saved.</td>
078 * </tr>
079 * <tr>
080 * <td>Xml.SaveInitial</td>
081 * <td>{@link Boolean}</td>
082 * <td>If true, initial solution is saved.</td>
083 * </tr>
084 * <tr>
085 * <td>Xml.SaveCurrent</td>
086 * <td>{@link Boolean}</td>
087 * <td>If true, current solution is saved.</td>
088 * </tr>
089 * <tr>
090 * <td>Xml.ExportStudentSectioning</td>
091 * <td>{@link Boolean}</td>
092 * <td>If true, student sectioning is saved even when there is no solution.</td>
093 * </tr>
094 * </table>
095 * 
096 * @version CourseTT 1.3 (University Course Timetabling)<br>
097 *          Copyright (C) 2006 - 2014 Tomas Muller<br>
098 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
099 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
100 * <br>
101 *          This library is free software; you can redistribute it and/or modify
102 *          it under the terms of the GNU Lesser General Public License as
103 *          published by the Free Software Foundation; either version 3 of the
104 *          License, or (at your option) any later version. <br>
105 * <br>
106 *          This library is distributed in the hope that it will be useful, but
107 *          WITHOUT ANY WARRANTY; without even the implied warranty of
108 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
109 *          Lesser General Public License for more details. <br>
110 * <br>
111 *          You should have received a copy of the GNU Lesser General Public
112 *          License along with this library; if not see
113 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
114 */
115
116public class TimetableXMLSaver extends TimetableSaver {
117    private static org.apache.log4j.Logger sLogger = org.apache.log4j.Logger.getLogger(TimetableXMLSaver.class);
118    private static DecimalFormat[] sDF = { new DecimalFormat(""), new DecimalFormat("0"), new DecimalFormat("00"),
119            new DecimalFormat("000"), new DecimalFormat("0000"), new DecimalFormat("00000"),
120            new DecimalFormat("000000"), new DecimalFormat("0000000") };
121    private static DecimalFormat sStudentWeightFormat = new DecimalFormat("0.0000", new DecimalFormatSymbols(Locale.US));
122    public static boolean ANONYMISE = false;
123
124    private boolean iConvertIds = false;
125    private boolean iShowNames = false;
126    private File iOutputFolder = null;
127    private boolean iSaveBest = false;
128    private boolean iSaveInitial = false;
129    private boolean iSaveCurrent = false;
130    private boolean iExportStudentSectioning = false;
131
132    private IdConvertor iIdConvertor = null;
133
134    public TimetableXMLSaver(Solver<Lecture, Placement> solver) {
135        super(solver);
136        
137        
138        iOutputFolder = new File(getModel().getProperties().getProperty("General.Output",
139                "." + File.separator + "output"));
140        iShowNames = getModel().getProperties().getPropertyBoolean("Xml.ShowNames", false);
141        iExportStudentSectioning = getModel().getProperties().getPropertyBoolean("Xml.ExportStudentSectioning", false);
142        if (ANONYMISE) {
143            // anonymise saved XML file -- if not set otherwise in the
144            // configuration
145            iConvertIds = getModel().getProperties().getPropertyBoolean("Xml.ConvertIds", true);
146            iSaveBest = getModel().getProperties().getPropertyBoolean("Xml.SaveBest", false);
147            iSaveInitial = getModel().getProperties().getPropertyBoolean("Xml.SaveInitial", false);
148            iSaveCurrent = getModel().getProperties().getPropertyBoolean("Xml.SaveCurrent", true);
149        } else {
150            // normal operation -- if not set otherwise in the configuration
151            iConvertIds = getModel().getProperties().getPropertyBoolean("Xml.ConvertIds", false);
152            iSaveBest = getModel().getProperties().getPropertyBoolean("Xml.SaveBest", true);
153            iSaveInitial = getModel().getProperties().getPropertyBoolean("Xml.SaveInitial", true);
154            iSaveCurrent = getModel().getProperties().getPropertyBoolean("Xml.SaveCurrent", true);
155        }
156    }
157
158    private String getId(String type, String id) {
159        if (!iConvertIds)
160            return id.toString();
161        if (iIdConvertor == null)
162            iIdConvertor = new IdConvertor(getModel().getProperties().getProperty("Xml.IdConv"));
163        return iIdConvertor.convert(type, id);
164    }
165
166    private String getId(String type, Number id) {
167        return getId(type, id.toString());
168    }
169
170    private static String bitset2string(BitSet b) {
171        StringBuffer sb = new StringBuffer();
172        for (int i = 0; i < b.length(); i++)
173            sb.append(b.get(i) ? "1" : "0");
174        return sb.toString();
175    }
176
177    @Override
178    public void save() throws Exception {
179        save(null);
180    }
181
182    public void save(File outFile) throws Exception {
183        if (outFile == null)
184            outFile = new File(iOutputFolder, "solution.xml");
185        outFile.getParentFile().mkdirs();
186        sLogger.debug("Writting XML data to:" + outFile);
187
188        Document document = DocumentHelper.createDocument();
189        document.addComment("University Course Timetabling");
190
191        if (iSaveCurrent && getAssignment().nrAssignedVariables() != 0) {
192            StringBuffer comments = new StringBuffer("Solution Info:\n");
193            Map<String, String> solutionInfo = (getSolution() == null ? getModel().getExtendedInfo(getAssignment()) : getSolution().getExtendedInfo());
194            for (String key : new TreeSet<String>(solutionInfo.keySet())) {
195                String value = solutionInfo.get(key);
196                comments.append("    " + key + ": " + value + "\n");
197            }
198            document.addComment(comments.toString());
199        }
200
201        Element root = document.addElement("timetable");
202        root.addAttribute("version", "2.5");
203        root.addAttribute("initiative", getModel().getProperties().getProperty("Data.Initiative"));
204        root.addAttribute("term", getModel().getProperties().getProperty("Data.Term"));
205        root.addAttribute("year", String.valueOf(getModel().getYear()));
206        root.addAttribute("created", String.valueOf(new Date()));
207        root.addAttribute("nrDays", String.valueOf(Constants.DAY_CODES.length));
208        root.addAttribute("slotsPerDay", String.valueOf(Constants.SLOTS_PER_DAY));
209        if (!iConvertIds && getModel().getProperties().getProperty("General.SessionId") != null)
210            root.addAttribute("session", getModel().getProperties().getProperty("General.SessionId"));
211        if (iShowNames && !iConvertIds && getModel().getProperties().getProperty("General.SolverGroupId") != null)
212            root.addAttribute("solverGroup", getId("solverGroup", getModel().getProperties().getProperty(
213                    "General.SolverGroupId")));
214
215        HashMap<String, Element> roomElements = new HashMap<String, Element>();
216
217        Element roomsEl = root.addElement("rooms");
218        for (RoomConstraint roomConstraint : getModel().getRoomConstraints()) {
219            Element roomEl = roomsEl.addElement("room").addAttribute("id",
220                    getId("room", roomConstraint.getResourceId()));
221            roomEl.addAttribute("constraint", "true");
222            if (roomConstraint instanceof DiscouragedRoomConstraint)
223                roomEl.addAttribute("discouraged", "true");
224            if (iShowNames) {
225                roomEl.addAttribute("name", roomConstraint.getRoomName());
226            }
227            if (!iConvertIds && roomConstraint.getBuildingId() != null)
228                roomEl.addAttribute("building", getId("bldg", roomConstraint.getBuildingId()));
229            roomElements.put(getId("room", roomConstraint.getResourceId()), roomEl);
230            roomEl.addAttribute("capacity", String.valueOf(roomConstraint.getCapacity()));
231            if (roomConstraint.getPosX() != null && roomConstraint.getPosY() != null)
232                roomEl.addAttribute("location", roomConstraint.getPosX() + "," + roomConstraint.getPosY());
233            if (roomConstraint.getIgnoreTooFar())
234                roomEl.addAttribute("ignoreTooFar", "true");
235            if (!roomConstraint.getConstraint())
236                roomEl.addAttribute("fake", "true");
237            if (roomConstraint.getSharingModel() != null) {
238                RoomSharingModel sharingModel = roomConstraint.getSharingModel();
239                Element sharingEl = roomEl.addElement("sharing");
240                sharingEl.addElement("pattern").addAttribute("unit", String.valueOf(sharingModel.getStep())).setText(sharingModel.getPreferences());
241                sharingEl.addElement("freeForAll").addAttribute("value",
242                        String.valueOf(sharingModel.getFreeForAllPrefChar()));
243                sharingEl.addElement("notAvailable").addAttribute("value",
244                        String.valueOf(sharingModel.getNotAvailablePrefChar()));
245                for (Long id: sharingModel.getDepartmentIds()) {
246                    sharingEl.addElement("department")
247                        .addAttribute("value", String.valueOf(sharingModel.getCharacter(id)))
248                        .addAttribute("id", getId("dept", id));
249                }
250            }
251            if (roomConstraint.getType() != null && iShowNames)
252                roomEl.addAttribute("type", roomConstraint.getType().toString());
253            
254            Map<Long, Integer> travelTimes = getModel().getDistanceMetric().getTravelTimes().get(roomConstraint.getResourceId());
255            if (travelTimes != null)
256                for (Map.Entry<Long, Integer> time: travelTimes.entrySet())
257                    roomEl.addElement("travel-time").addAttribute("id", getId("room", time.getKey())).addAttribute("minutes", time.getValue().toString());
258        }
259
260        Element instructorsEl = root.addElement("instructors");
261
262        Element departmentsEl = root.addElement("departments");
263        HashMap<Long, String> depts = new HashMap<Long, String>();
264
265        Element configsEl = (iShowNames ? root.addElement("configurations") : null);
266        HashSet<Configuration> configs = new HashSet<Configuration>();
267
268        Element classesEl = root.addElement("classes");
269        HashMap<Long, Element> classElements = new HashMap<Long, Element>();
270        List<Lecture> vars = new ArrayList<Lecture>(getModel().variables());
271        if (getModel().hasConstantVariables())
272            vars.addAll(getModel().constantVariables());
273        for (Lecture lecture : vars) {
274            Placement placement = getAssignment().getValue(lecture);
275            if (lecture.isCommitted() && placement == null)
276                placement = lecture.getInitialAssignment();
277            Placement initialPlacement = lecture.getInitialAssignment();
278            // if (initialPlacement==null) initialPlacement =
279            // (Placement)lecture.getAssignment();
280            Placement bestPlacement = lecture.getBestAssignment();
281            Element classEl = classesEl.addElement("class").addAttribute("id", getId("class", lecture.getClassId()));
282            classElements.put(lecture.getClassId(), classEl);
283            if (iShowNames && lecture.getNote() != null)
284                classEl.addAttribute("note", lecture.getNote());
285            if (iShowNames && !lecture.isCommitted())
286                classEl.addAttribute("ord", String.valueOf(lecture.getOrd()));
287            if (lecture.getWeight() != 1.0)
288                classEl.addAttribute("weight", String.valueOf(lecture.getWeight()));
289            if (iShowNames && lecture.getSolverGroupId() != null)
290                classEl.addAttribute("solverGroup", getId("solverGroup", lecture.getSolverGroupId()));
291            if (lecture.getParent() == null && lecture.getConfiguration() != null) {
292                if (!iShowNames)
293                    classEl.addAttribute("offering", getId("offering", lecture.getConfiguration().getOfferingId()
294                            .toString()));
295                classEl.addAttribute("config", getId("config", lecture.getConfiguration().getConfigId().toString()));
296                if (iShowNames && configs.add(lecture.getConfiguration())) {
297                    configsEl.addElement("config").addAttribute("id",
298                            getId("config", lecture.getConfiguration().getConfigId().toString())).addAttribute("limit",
299                            String.valueOf(lecture.getConfiguration().getLimit())).addAttribute("offering",
300                            getId("offering", lecture.getConfiguration().getOfferingId().toString()));
301                }
302            }
303            classEl.addAttribute("committed", (lecture.isCommitted() ? "true" : "false"));
304            if (lecture.getParent() != null)
305                classEl.addAttribute("parent", getId("class", lecture.getParent().getClassId()));
306            if (lecture.getSchedulingSubpartId() != null)
307                classEl.addAttribute("subpart", getId("subpart", lecture.getSchedulingSubpartId()));
308            if (iShowNames && lecture.isCommitted() && placement != null && placement.getAssignmentId() != null) {
309                classEl.addAttribute("assignment", getId("assignment", placement.getAssignmentId()));
310            }
311            if (!lecture.isCommitted()) {
312                if (lecture.minClassLimit() == lecture.maxClassLimit()) {
313                    classEl.addAttribute("classLimit", String.valueOf(lecture.maxClassLimit()));
314                } else {
315                    classEl.addAttribute("minClassLimit", String.valueOf(lecture.minClassLimit()));
316                    classEl.addAttribute("maxClassLimit", String.valueOf(lecture.maxClassLimit()));
317                }
318                if (lecture.roomToLimitRatio() != 1.0f)
319                    classEl.addAttribute("roomToLimitRatio", sStudentWeightFormat.format(lecture.roomToLimitRatio()));
320            }
321            if (lecture.getNrRooms() != 1)
322                classEl.addAttribute("nrRooms", String.valueOf(lecture.getNrRooms()));
323            if (lecture.getNrRooms() > 1 && lecture.getMaxRoomCombinations() > 0)
324                classEl.addAttribute("maxRoomCombinations", String.valueOf(lecture.getMaxRoomCombinations()));
325            if (iShowNames)
326                classEl.addAttribute("name", lecture.getName());
327            if (lecture.getDeptSpreadConstraint() != null) {
328                classEl.addAttribute("department", getId("dept", lecture.getDeptSpreadConstraint().getDepartmentId()));
329                depts.put(lecture.getDeptSpreadConstraint().getDepartmentId(), lecture.getDeptSpreadConstraint()
330                        .getName());
331            }
332            if (lecture.getScheduler() != null)
333                classEl.addAttribute("scheduler", getId("dept", lecture.getScheduler()));
334            for (InstructorConstraint ic : lecture.getInstructorConstraints()) {
335                Element instrEl = classEl.addElement("instructor")
336                        .addAttribute("id", getId("inst", ic.getResourceId()));
337                if ((lecture.isCommitted() || iSaveCurrent) && placement != null)
338                    instrEl.addAttribute("solution", "true");
339                if (iSaveInitial && initialPlacement != null)
340                    instrEl.addAttribute("initial", "true");
341                if (iSaveBest && bestPlacement != null && !bestPlacement.equals(placement))
342                    instrEl.addAttribute("best", "true");
343            }
344            for (RoomLocation rl : lecture.roomLocations()) {
345                Element roomLocationEl = classEl.addElement("room");
346                roomLocationEl.addAttribute("id", getId("room", rl.getId()));
347                roomLocationEl.addAttribute("pref", String.valueOf(rl.getPreference()));
348                if ((lecture.isCommitted() || iSaveCurrent) && placement != null
349                        && placement.hasRoomLocation(rl.getId()))
350                    roomLocationEl.addAttribute("solution", "true");
351                if (iSaveInitial && initialPlacement != null && initialPlacement.hasRoomLocation(rl.getId()))
352                    roomLocationEl.addAttribute("initial", "true");
353                if (iSaveBest && bestPlacement != null && !bestPlacement.equals(placement)
354                        && bestPlacement.hasRoomLocation(rl.getId()))
355                    roomLocationEl.addAttribute("best", "true");
356                if (!roomElements.containsKey(getId("room", rl.getId()))) {
357                    // room location without room constraint
358                    Element roomEl = roomsEl.addElement("room").addAttribute("id", getId("room", rl.getId()));
359                    roomEl.addAttribute("constraint", "false");
360                    if (!iConvertIds && rl.getBuildingId() != null)
361                        roomEl.addAttribute("building", getId("bldg", rl.getBuildingId()));
362                    if (iShowNames) {
363                        roomEl.addAttribute("name", rl.getName());
364                    }
365                    roomElements.put(getId("room", rl.getId()), roomEl);
366                    roomEl.addAttribute("capacity", String.valueOf(rl.getRoomSize()));
367                    if (rl.getPosX() != null && rl.getPosY() != null)
368                        roomEl.addAttribute("location", rl.getPosX() + "," + rl.getPosY());
369                    if (rl.getIgnoreTooFar())
370                        roomEl.addAttribute("ignoreTooFar", "true");
371                }
372            }
373            boolean first = true;
374            Set<Long> dp = new HashSet<Long>();
375            for (TimeLocation tl : lecture.timeLocations()) {
376                Element timeLocationEl = classEl.addElement("time");
377                timeLocationEl.addAttribute("days", sDF[7].format(Long.parseLong(Integer
378                        .toBinaryString(tl.getDayCode()))));
379                timeLocationEl.addAttribute("start", String.valueOf(tl.getStartSlot()));
380                timeLocationEl.addAttribute("length", String.valueOf(tl.getLength()));
381                timeLocationEl.addAttribute("breakTime", String.valueOf(tl.getBreakTime()));
382                if (iShowNames) {
383                    timeLocationEl.addAttribute("pref", String.valueOf(tl.getPreference()));
384                    timeLocationEl.addAttribute("npref", String.valueOf(tl.getNormalizedPreference()));
385                } else {
386                    timeLocationEl.addAttribute("pref", String.valueOf(tl.getNormalizedPreference()));
387                }
388                if (!iConvertIds && tl.getTimePatternId() != null)
389                    timeLocationEl.addAttribute("pattern", getId("pat", tl.getTimePatternId()));
390                if (tl.getDatePatternId() != null && dp.add(tl.getDatePatternId())) {
391                    Element dateEl = classEl.addElement("date");
392                    dateEl.addAttribute("id", getId("dpat", String.valueOf(tl.getDatePatternId())));
393                    if (iShowNames)
394                        dateEl.addAttribute("name", tl.getDatePatternName());
395                    dateEl.addAttribute("pattern", bitset2string(tl.getWeekCode()));
396                }
397                if (tl.getDatePatternPreference() != 0)
398                    timeLocationEl.addAttribute("datePref", String.valueOf(tl.getDatePatternPreference()));
399                if (tl.getTimePatternId() == null && first) {
400                    if (iShowNames)
401                        classEl.addAttribute("datePatternName", tl.getDatePatternName());
402                    classEl.addAttribute("dates", bitset2string(tl.getWeekCode()));
403                    first = false;
404                }
405                if (tl.getDatePatternId() != null) {
406                    timeLocationEl.addAttribute("date", getId("dpat", String.valueOf(tl.getDatePatternId())));
407                }
408                if ((lecture.isCommitted() || iSaveCurrent) && placement != null
409                        && placement.getTimeLocation().equals(tl))
410                    timeLocationEl.addAttribute("solution", "true");
411                if (iSaveInitial && initialPlacement != null && initialPlacement.getTimeLocation().equals(tl))
412                    timeLocationEl.addAttribute("initial", "true");
413                if (iSaveBest && bestPlacement != null && !bestPlacement.equals(placement)
414                        && bestPlacement.getTimeLocation().equals(tl))
415                    timeLocationEl.addAttribute("best", "true");
416            }
417        }
418
419        for (InstructorConstraint ic : getModel().getInstructorConstraints()) {
420            if (iShowNames || ic.isIgnoreDistances()) {
421                Element instrEl = instructorsEl.addElement("instructor").addAttribute("id",
422                        getId("inst", ic.getResourceId()));
423                if (iShowNames) {
424                    if (ic.getPuid() != null && ic.getPuid().length() > 0)
425                        instrEl.addAttribute("puid", ic.getPuid());
426                    instrEl.addAttribute("name", ic.getName());
427                    if (ic.getType() != null && iShowNames)
428                        instrEl.addAttribute("type", ic.getType().toString());
429                }
430                if (ic.isIgnoreDistances()) {
431                    instrEl.addAttribute("ignDist", "true");
432                }
433            }
434            if (ic.getUnavailabilities() != null) {
435                for (Placement placement: ic.getUnavailabilities()) {
436                    Lecture lecture = placement.variable();
437                    Element classEl = classElements.get(lecture.getClassId());
438                    classEl.addElement("instructor").addAttribute("id", getId("inst", ic.getResourceId())).addAttribute("solution", "true");
439                }
440            }
441        }
442        if (instructorsEl.elements().isEmpty())
443            root.remove(instructorsEl);
444
445        Element grConstraintsEl = root.addElement("groupConstraints");
446        for (GroupConstraint gc : getModel().getGroupConstraints()) {
447            Element grEl = grConstraintsEl.addElement("constraint").addAttribute("id",
448                    getId("gr", String.valueOf(gc.getId())));
449            grEl.addAttribute("type", gc.getType().reference());
450            grEl.addAttribute("pref", gc.getPrologPreference());
451            for (Lecture l : gc.variables()) {
452                grEl.addElement("class").addAttribute("id", getId("class", l.getClassId()));
453            }
454        }       
455        for (SpreadConstraint spread : getModel().getSpreadConstraints()) {
456            Element grEl = grConstraintsEl.addElement("constraint").addAttribute("id",
457                    getId("gr", String.valueOf(spread.getId())));
458            grEl.addAttribute("type", "SPREAD");
459            grEl.addAttribute("pref", Constants.sPreferenceRequired);
460            if (iShowNames)
461                grEl.addAttribute("name", spread.getName());
462            for (Lecture l : spread.variables()) {
463                grEl.addElement("class").addAttribute("id", getId("class", l.getClassId()));
464            }
465        }
466        for (Constraint<Lecture, Placement> c : getModel().constraints()) {
467            if (c instanceof MinimizeNumberOfUsedRoomsConstraint) {
468                Element grEl = grConstraintsEl.addElement("constraint").addAttribute("id",
469                        getId("gr", String.valueOf(c.getId())));
470                grEl.addAttribute("type", "MIN_ROOM_USE");
471                grEl.addAttribute("pref", Constants.sPreferenceRequired);
472                for (Lecture l : c.variables()) {
473                    grEl.addElement("class").addAttribute("id", getId("class", l.getClassId()));
474                }
475            }
476            if (c instanceof MinimizeNumberOfUsedGroupsOfTime) {
477                Element grEl = grConstraintsEl.addElement("constraint").addAttribute("id",
478                        getId("gr", String.valueOf(c.getId())));
479                grEl.addAttribute("type", ((MinimizeNumberOfUsedGroupsOfTime) c).getConstraintName());
480                grEl.addAttribute("pref", Constants.sPreferenceRequired);
481                for (Lecture l : c.variables()) {
482                    grEl.addElement("class").addAttribute("id", getId("class", l.getClassId()));
483                }
484            }
485            if (c instanceof IgnoreStudentConflictsConstraint) {
486                Element grEl = grConstraintsEl.addElement("constraint").addAttribute("id", getId("gr", String.valueOf(c.getId())));
487                grEl.addAttribute("type", IgnoreStudentConflictsConstraint.REFERENCE);
488                grEl.addAttribute("pref", Constants.sPreferenceRequired);
489                for (Lecture l : c.variables()) {
490                    grEl.addElement("class").addAttribute("id", getId("class", l.getClassId()));
491                }
492            }
493        }
494        for (ClassLimitConstraint clc : getModel().getClassLimitConstraints()) {
495            Element grEl = grConstraintsEl.addElement("constraint").addAttribute("id",
496                    getId("gr", String.valueOf(clc.getId())));
497            grEl.addAttribute("type", "CLASS_LIMIT");
498            grEl.addAttribute("pref", Constants.sPreferenceRequired);
499            if (clc.getParentLecture() != null) {
500                grEl.addElement("parentClass").addAttribute("id", getId("class", clc.getParentLecture().getClassId()));
501            } else
502                grEl.addAttribute("courseLimit", String.valueOf(clc.classLimit() - clc.getClassLimitDelta()));
503            if (clc.getClassLimitDelta() != 0)
504                grEl.addAttribute("delta", String.valueOf(clc.getClassLimitDelta()));
505            if (iShowNames)
506                grEl.addAttribute("name", clc.getName());
507            for (Lecture l : clc.variables()) {
508                grEl.addElement("class").addAttribute("id", getId("class", l.getClassId()));
509            }
510        }
511        for (FlexibleConstraint gc : getModel().getFlexibleConstraints()) {
512            Element flEl = grConstraintsEl.addElement("constraint").addAttribute("id",
513                    getId("gr", String.valueOf(gc.getId())));
514            flEl.addAttribute("reference", gc.getReference());
515            flEl.addAttribute("owner", gc.getOwner());
516            flEl.addAttribute("pref", gc.getPrologPreference());  
517            flEl.addAttribute("type", gc.getType().toString());  
518            for (Lecture l : gc.variables()) {
519                flEl.addElement("class").addAttribute("id", getId("class", l.getClassId()));
520            }
521        }
522
523        HashMap<Student, List<String>> students = new HashMap<Student, List<String>>();
524        for (Lecture lecture : vars) {
525            for (Student student : lecture.students()) {
526                List<String> enrls = students.get(student);
527                if (enrls == null) {
528                    enrls = new ArrayList<String>();
529                    students.put(student, enrls);
530                }
531                enrls.add(getId("class", lecture.getClassId()));
532            }
533        }
534
535        Element studentsEl = root.addElement("students");
536        for (Student student: new TreeSet<Student>(students.keySet())) {
537            Element stEl = studentsEl.addElement("student").addAttribute("id", getId("student", student.getId()));
538            if (iShowNames) {
539                if (student.getAcademicArea() != null)
540                    stEl.addAttribute("area", student.getAcademicArea());
541                if (student.getAcademicClassification() != null)
542                    stEl.addAttribute("classification", student.getAcademicClassification());
543                if (student.getMajor() != null)
544                    stEl.addAttribute("major", student.getMajor());
545                if (student.getCurriculum() != null)
546                    stEl.addAttribute("curriculum", student.getCurriculum());
547            }
548            for (Map.Entry<Long, Double> entry : student.getOfferingsMap().entrySet()) {
549                Long offeringId = entry.getKey();
550                Double weight = entry.getValue();
551                Element offEl = stEl.addElement("offering")
552                        .addAttribute("id", getId("offering", offeringId.toString()));
553                if (weight.doubleValue() != 1.0)
554                    offEl.addAttribute("weight", sStudentWeightFormat.format(weight));
555                Double priority = student.getPriority(offeringId);
556                if (priority != null)
557                    offEl.addAttribute("priority", priority.toString());
558            }
559            if (iExportStudentSectioning || getModel().nrUnassignedVariables(getAssignment()) == 0 || student.getOfferingsMap().isEmpty()) {
560                List<String> lectures = students.get(student);
561                Collections.sort(lectures);
562                for (String classId : lectures) {
563                    stEl.addElement("class").addAttribute("id", classId);
564                }
565            }
566            Map<Long, Set<Lecture>> canNotEnroll = student.canNotEnrollSections();
567            if (canNotEnroll != null) {
568                for (Set<Lecture> canNotEnrollLects: canNotEnroll.values()) {
569                    for (Iterator<Lecture> i3 = canNotEnrollLects.iterator(); i3.hasNext();) {
570                        stEl.addElement("prohibited-class")
571                                .addAttribute("id", getId("class", (i3.next()).getClassId()));
572                    }
573                }
574            }
575
576            if (student.getCommitedPlacements() != null) {
577                for (Placement placement : student.getCommitedPlacements()) {
578                    stEl.addElement("class").addAttribute("id", getId("class", placement.variable().getClassId()));
579                }
580            }
581            
582            if (student.getInstructor() != null)
583                stEl.addAttribute("instructor", getId("inst", student.getInstructor().getResourceId()));
584        }
585
586        if (getModel().getProperties().getPropertyInt("MPP.GenTimePert", 0) > 0) {
587            Element perturbationsEl = root.addElement("perturbations");
588            int nrChanges = getModel().getProperties().getPropertyInt("MPP.GenTimePert", 0);
589            List<Lecture> lectures = new ArrayList<Lecture>();
590            while (lectures.size() < nrChanges) {
591                Lecture lecture = ToolBox.random(getAssignment().assignedVariables());
592                if (lecture.isCommitted() || lecture.timeLocations().size() <= 1 || lectures.contains(lecture))
593                    continue;
594                Placement placement = getAssignment().getValue(lecture);
595                TimeLocation tl = placement.getTimeLocation();
596                perturbationsEl.addElement("class").addAttribute("id", getId("class", lecture.getClassId()))
597                        .addAttribute("days", sDF[7].format(Long.parseLong(Integer.toBinaryString(tl.getDayCode()))))
598                        .addAttribute("start", String.valueOf(tl.getStartSlot())).addAttribute("length",
599                                String.valueOf(tl.getLength()));
600                lectures.add(lecture);
601            }
602        }
603
604        for (Map.Entry<Long, String> entry : depts.entrySet()) {
605            Long id = entry.getKey();
606            String name = entry.getValue();
607            if (iShowNames) {
608                departmentsEl.addElement("department").addAttribute("id", getId("dept", id.toString())).addAttribute(
609                        "name", name);
610            }
611        }
612        if (departmentsEl.elements().isEmpty())
613            root.remove(departmentsEl);
614
615        if (iShowNames) {
616            Progress.getInstance(getModel()).save(root);
617
618            try {
619                getSolver().getClass().getMethod("save", new Class[] { Element.class }).invoke(getSolver(),
620                        new Object[] { root });
621            } catch (Exception e) {
622            }
623        }
624
625        FileOutputStream fos = null;
626        try {
627            fos = new FileOutputStream(outFile);
628            (new XMLWriter(fos, OutputFormat.createPrettyPrint())).write(document);
629            fos.flush();
630            fos.close();
631            fos = null;
632        } finally {
633            try {
634                if (fos != null)
635                    fos.close();
636            } catch (IOException e) {
637            }
638        }
639
640        if (iConvertIds)
641            iIdConvertor.save();
642    }
643}