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