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