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