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