001package org.cpsolver.studentsct;
002
003import java.io.File;
004import java.io.FileOutputStream;
005import java.io.IOException;
006import java.math.RoundingMode;
007import java.text.DecimalFormat;
008import java.text.DecimalFormatSymbols;
009import java.util.BitSet;
010import java.util.Date;
011import java.util.Locale;
012import java.util.Map;
013import java.util.Set;
014import java.util.TreeSet;
015
016import org.cpsolver.coursett.IdConvertor;
017import org.cpsolver.coursett.model.RoomLocation;
018import org.cpsolver.coursett.model.TimeLocation;
019import org.cpsolver.ifs.solver.Solver;
020import org.cpsolver.ifs.util.Progress;
021import org.cpsolver.studentsct.constraint.LinkedSections;
022import org.cpsolver.studentsct.model.AcademicAreaCode;
023import org.cpsolver.studentsct.model.Choice;
024import org.cpsolver.studentsct.model.Config;
025import org.cpsolver.studentsct.model.Course;
026import org.cpsolver.studentsct.model.CourseRequest;
027import org.cpsolver.studentsct.model.Enrollment;
028import org.cpsolver.studentsct.model.FreeTimeRequest;
029import org.cpsolver.studentsct.model.Offering;
030import org.cpsolver.studentsct.model.Request;
031import org.cpsolver.studentsct.model.Section;
032import org.cpsolver.studentsct.model.Student;
033import org.cpsolver.studentsct.model.Subpart;
034import org.cpsolver.studentsct.reservation.CourseReservation;
035import org.cpsolver.studentsct.reservation.CurriculumReservation;
036import org.cpsolver.studentsct.reservation.DummyReservation;
037import org.cpsolver.studentsct.reservation.GroupReservation;
038import org.cpsolver.studentsct.reservation.IndividualReservation;
039import org.cpsolver.studentsct.reservation.Reservation;
040import org.cpsolver.studentsct.reservation.ReservationOverride;
041import org.dom4j.Document;
042import org.dom4j.DocumentHelper;
043import org.dom4j.Element;
044import org.dom4j.io.OutputFormat;
045import org.dom4j.io.XMLWriter;
046
047
048/**
049 * Save student sectioning solution into an XML file.
050 * 
051 * <br>
052 * <br>
053 * Parameters:
054 * <table border='1' summary='Related Solver Parameters'>
055 * <tr>
056 * <th>Parameter</th>
057 * <th>Type</th>
058 * <th>Comment</th>
059 * </tr>
060 * <tr>
061 * <td>General.Output</td>
062 * <td>{@link String}</td>
063 * <td>Folder with the output solution in XML format (solution.xml)</td>
064 * </tr>
065 * <tr>
066 * <td>Xml.ConvertIds</td>
067 * <td>{@link Boolean}</td>
068 * <td>If true, ids are converted (to be able to make input data public)</td>
069 * </tr>
070 * <tr>
071 * <td>Xml.ShowNames</td>
072 * <td>{@link Boolean}</td>
073 * <td>If false, names are not exported (to be able to make input data public)</td>
074 * </tr>
075 * <tr>
076 * <td>Xml.SaveBest</td>
077 * <td>{@link Boolean}</td>
078 * <td>If true, best solution is saved.</td>
079 * </tr>
080 * <tr>
081 * <td>Xml.SaveInitial</td>
082 * <td>{@link Boolean}</td>
083 * <td>If true, initial solution is saved.</td>
084 * </tr>
085 * <tr>
086 * <td>Xml.SaveCurrent</td>
087 * <td>{@link Boolean}</td>
088 * <td>If true, current solution is saved.</td>
089 * </tr>
090 * <tr>
091 * <td>Xml.SaveOnlineSectioningInfo</td>
092 * <td>{@link Boolean}</td>
093 * <td>If true, save online sectioning info (i.e., expected and held space of
094 * each section)</td>
095 * </tr>
096 * <tr>
097 * <td>Xml.SaveStudentInfo</td>
098 * <td>{@link Boolean}</td>
099 * <td>If true, save student information (i.e., academic area classification,
100 * major, minor)</td>
101 * </tr>
102 * </table>
103 * <br>
104 * <br>
105 * Usage:
106 * <pre><code>
107 * new StudentSectioningXMLSaver(solver).save(new File("solution.xml")); 
108 * </code></pre>
109 * 
110 * @version StudentSct 1.3 (Student Sectioning)<br>
111 *          Copyright (C) 2007 - 2014 Tomas Muller<br>
112 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
113 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
114 * <br>
115 *          This library is free software; you can redistribute it and/or modify
116 *          it under the terms of the GNU Lesser General Public License as
117 *          published by the Free Software Foundation; either version 3 of the
118 *          License, or (at your option) any later version. <br>
119 * <br>
120 *          This library is distributed in the hope that it will be useful, but
121 *          WITHOUT ANY WARRANTY; without even the implied warranty of
122 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
123 *          Lesser General Public License for more details. <br>
124 * <br>
125 *          You should have received a copy of the GNU Lesser General Public
126 *          License along with this library; if not see
127 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
128 */
129
130public class StudentSectioningXMLSaver extends StudentSectioningSaver {
131    private static org.apache.log4j.Logger sLogger = org.apache.log4j.Logger.getLogger(StudentSectioningXMLSaver.class);
132    private static DecimalFormat[] sDF = { new DecimalFormat(""), new DecimalFormat("0"), new DecimalFormat("00"),
133            new DecimalFormat("000"), new DecimalFormat("0000"), new DecimalFormat("00000"),
134            new DecimalFormat("000000"), new DecimalFormat("0000000") };
135    private static DecimalFormat sStudentWeightFormat = new DecimalFormat("0.0000", new DecimalFormatSymbols(Locale.US));
136    private File iOutputFolder = null;
137
138    private boolean iSaveBest = false;
139    private boolean iSaveInitial = false;
140    private boolean iSaveCurrent = false;
141    private boolean iSaveOnlineSectioningInfo = false;
142    private boolean iSaveStudentInfo = true;
143
144    private boolean iConvertIds = false;
145    private boolean iShowNames = false;
146    
147    static {
148        sStudentWeightFormat.setRoundingMode(RoundingMode.DOWN);
149    }
150
151    /**
152     * Constructor
153     * 
154     * @param solver
155     *            student sectioning solver
156     */
157    public StudentSectioningXMLSaver(Solver<Request, Enrollment> solver) {
158        super(solver);
159        iOutputFolder = new File(getModel().getProperties().getProperty("General.Output",
160                "." + File.separator + "output"));
161        iSaveBest = getModel().getProperties().getPropertyBoolean("Xml.SaveBest", true);
162        iSaveInitial = getModel().getProperties().getPropertyBoolean("Xml.SaveInitial", true);
163        iSaveCurrent = getModel().getProperties().getPropertyBoolean("Xml.SaveCurrent", false);
164        iSaveOnlineSectioningInfo = getModel().getProperties().getPropertyBoolean("Xml.SaveOnlineSectioningInfo", true);
165        iSaveStudentInfo = getModel().getProperties().getPropertyBoolean("Xml.SaveStudentInfo", true);
166        iShowNames = getModel().getProperties().getPropertyBoolean("Xml.ShowNames", true);
167        iConvertIds = getModel().getProperties().getPropertyBoolean("Xml.ConvertIds", false);
168    }
169
170    /** Convert bitset to a bit string */
171    private static String bitset2string(BitSet b) {
172        StringBuffer sb = new StringBuffer();
173        for (int i = 0; i < b.length(); i++)
174            sb.append(b.get(i) ? "1" : "0");
175        return sb.toString();
176    }
177
178    /** Generate id for given object with the given id */
179    private String getId(String type, String id) {
180        if (!iConvertIds)
181            return id.toString();
182        return IdConvertor.getInstance().convert(type, id);
183    }
184
185    /** Generate id for given object with the given id */
186    private String getId(String type, Number id) {
187        return getId(type, id.toString());
188    }
189
190    /** Generate id for given object with the given id */
191    private String getId(String type, long id) {
192        return getId(type, String.valueOf(id));
193    }
194
195    /** Save an XML file */
196    @Override
197    public void save() throws Exception {
198        save(null);
199    }
200
201    /**
202     * Save an XML file
203     * 
204     * @param outFile
205     *            output file
206     * @throws Exception thrown when the save fails
207     */
208    public void save(File outFile) throws Exception {
209        if (outFile == null) {
210            outFile = new File(iOutputFolder, "solution.xml");
211        } else if (outFile.getParentFile() != null) {
212            outFile.getParentFile().mkdirs();
213        }
214        sLogger.debug("Writting XML data to:" + outFile);
215
216        Document document = DocumentHelper.createDocument();
217        document.addComment("Student Sectioning");
218
219        if ((iSaveCurrent || iSaveBest)) { // &&
220                                           // !getModel().assignedVariables().isEmpty()
221            StringBuffer comments = new StringBuffer("Solution Info:\n");
222            Map<String, String> solutionInfo = (getSolution() == null ? getModel().getExtendedInfo(getAssignment()) : getSolution().getExtendedInfo());
223            for (String key : new TreeSet<String>(solutionInfo.keySet())) {
224                String value = solutionInfo.get(key);
225                comments.append("    " + key + ": " + value + "\n");
226            }
227            document.addComment(comments.toString());
228        }
229
230        Element root = document.addElement("sectioning");
231        root.addAttribute("version", "1.0");
232        root.addAttribute("initiative", getModel().getProperties().getProperty("Data.Initiative"));
233        root.addAttribute("term", getModel().getProperties().getProperty("Data.Term"));
234        root.addAttribute("year", getModel().getProperties().getProperty("Data.Year"));
235        root.addAttribute("created", String.valueOf(new Date()));
236
237        Element offeringsEl = root.addElement("offerings");
238        for (Offering offering : getModel().getOfferings()) {
239            Element offeringEl = offeringsEl.addElement("offering");
240            offeringEl.addAttribute("id", getId("offering", offering.getId()));
241            if (iShowNames)
242                offeringEl.addAttribute("name", offering.getName());
243            for (Course course : offering.getCourses()) {
244                Element courseEl = offeringEl.addElement("course");
245                courseEl.addAttribute("id", getId("course", course.getId()));
246                if (iShowNames)
247                    courseEl.addAttribute("subjectArea", course.getSubjectArea());
248                if (iShowNames)
249                    courseEl.addAttribute("courseNbr", course.getCourseNumber());
250                if (iShowNames && course.getLimit() >= 0)
251                    courseEl.addAttribute("limit", String.valueOf(course.getLimit()));
252                if (iShowNames && course.getProjected() != 0)
253                    courseEl.addAttribute("projected", String.valueOf(course.getProjected()));
254            }
255            for (Config config : offering.getConfigs()) {
256                Element configEl = offeringEl.addElement("config");
257                configEl.addAttribute("id", getId("config", config.getId()));
258                if (config.getLimit() >= 0)
259                    configEl.addAttribute("limit", String.valueOf(config.getLimit()));
260                if (iShowNames)
261                    configEl.addAttribute("name", config.getName());
262                for (Subpart subpart : config.getSubparts()) {
263                    Element subpartEl = configEl.addElement("subpart");
264                    subpartEl.addAttribute("id", getId("subpart", subpart.getId()));
265                    subpartEl.addAttribute("itype", subpart.getInstructionalType());
266                    if (subpart.getParent() != null)
267                        subpartEl.addAttribute("parent", getId("subpart", subpart.getParent().getId()));
268                    if (iShowNames)
269                        subpartEl.addAttribute("name", subpart.getName());
270                    if (subpart.isAllowOverlap())
271                        subpartEl.addAttribute("allowOverlap", "true");
272                    for (Section section : subpart.getSections()) {
273                        Element sectionEl = subpartEl.addElement("section");
274                        sectionEl.addAttribute("id", getId("section", section.getId()));
275                        sectionEl.addAttribute("limit", String.valueOf(section.getLimit()));
276                        if (section.getNameByCourse() != null)
277                            for (Map.Entry<Long, String> entry: section.getNameByCourse().entrySet())
278                                sectionEl.addElement("cname").addAttribute("id", entry.getKey().toString()).setText(entry.getValue());
279                        if (section.getParent() != null)
280                            sectionEl.addAttribute("parent", getId("section", section.getParent().getId()));
281                        if (iShowNames && section.getChoice().getInstructorIds() != null)
282                            sectionEl.addAttribute("instructorIds", section.getChoice().getInstructorIds());
283                        if (iShowNames && section.getChoice().getInstructorNames() != null)
284                            sectionEl.addAttribute("instructorNames", section.getChoice().getInstructorNames());
285                        if (iShowNames)
286                            sectionEl.addAttribute("name", section.getName());
287                        if (section.getPlacement() != null) {
288                            TimeLocation tl = section.getPlacement().getTimeLocation();
289                            if (tl != null) {
290                                Element timeLocationEl = sectionEl.addElement("time");
291                                timeLocationEl.addAttribute("days", sDF[7].format(Long.parseLong(Integer
292                                        .toBinaryString(tl.getDayCode()))));
293                                timeLocationEl.addAttribute("start", String.valueOf(tl.getStartSlot()));
294                                timeLocationEl.addAttribute("length", String.valueOf(tl.getLength()));
295                                if (tl.getBreakTime() != 0)
296                                    timeLocationEl.addAttribute("breakTime", String.valueOf(tl.getBreakTime()));
297                                if (iShowNames && tl.getTimePatternId() != null)
298                                    timeLocationEl.addAttribute("pattern", getId("timePattern", tl.getTimePatternId()));
299                                if (iShowNames && tl.getDatePatternId() != null)
300                                    timeLocationEl.addAttribute("datePattern", tl.getDatePatternId().toString());
301                                if (iShowNames && tl.getDatePatternName() != null
302                                        && tl.getDatePatternName().length() > 0)
303                                    timeLocationEl.addAttribute("datePatternName", tl.getDatePatternName());
304                                timeLocationEl.addAttribute("dates", bitset2string(tl.getWeekCode()));
305                                if (iShowNames)
306                                    timeLocationEl.setText(tl.getLongName(true));
307                            }
308                            for (RoomLocation rl : section.getRooms()) {
309                                Element roomLocationEl = sectionEl.addElement("room");
310                                roomLocationEl.addAttribute("id", getId("room", rl.getId()));
311                                if (iShowNames && rl.getBuildingId() != null)
312                                    roomLocationEl.addAttribute("building", getId("building", rl.getBuildingId()));
313                                if (iShowNames && rl.getName() != null)
314                                    roomLocationEl.addAttribute("name", rl.getName());
315                                roomLocationEl.addAttribute("capacity", String.valueOf(rl.getRoomSize()));
316                                if (rl.getPosX() != null && rl.getPosY() != null)
317                                    roomLocationEl.addAttribute("location", rl.getPosX() + "," + rl.getPosY());
318                                if (rl.getIgnoreTooFar())
319                                    roomLocationEl.addAttribute("ignoreTooFar", "true");
320                            }
321                        }
322                        if (iSaveOnlineSectioningInfo) {
323                            if (section.getSpaceHeld() != 0.0)
324                                sectionEl.addAttribute("hold", sStudentWeightFormat.format(section.getSpaceHeld()));
325                            if (section.getSpaceExpected() != 0.0)
326                                sectionEl.addAttribute("expect", sStudentWeightFormat
327                                        .format(section.getSpaceExpected()));
328                        }
329                        if (section.getIgnoreConflictWithSectionIds() != null && !section.getIgnoreConflictWithSectionIds().isEmpty()) {
330                            Element ignoreEl = sectionEl.addElement("no-conflicts");
331                            for (Long sectionId: section.getIgnoreConflictWithSectionIds())
332                                ignoreEl.addElement("section").addAttribute("id", getId("section", sectionId));
333                        }
334                    }
335                }
336            }
337            if (!offering.getReservations().isEmpty()) {
338                for (Reservation r: offering.getReservations()) {
339                    Element reservationEl = offeringEl.addElement("reservation");
340                    reservationEl.addAttribute("id", getId("reservation", r.getId()));
341                    reservationEl.addAttribute("expired", r.isExpired() ? "true" : "false");
342                    if (r instanceof GroupReservation) {
343                        GroupReservation gr = (GroupReservation)r;
344                        reservationEl.addAttribute("type", "group");
345                        for (Long studentId: gr.getStudentIds())
346                            reservationEl.addElement("student").addAttribute("id", getId("student", studentId));
347                        if (gr.getReservationLimit() >= 0.0)
348                            reservationEl.addAttribute("limit", String.valueOf(gr.getReservationLimit()));
349                    } else if (r instanceof ReservationOverride) {
350                        reservationEl.addAttribute("type", "override");
351                        ReservationOverride o = (ReservationOverride)r;
352                        for (Long studentId: o.getStudentIds())
353                            reservationEl.addElement("student").addAttribute("id", getId("student", studentId));
354                    } else if (r instanceof IndividualReservation) {
355                        reservationEl.addAttribute("type", "individual");
356                        for (Long studentId: ((IndividualReservation)r).getStudentIds())
357                            reservationEl.addElement("student").addAttribute("id", getId("student", studentId));
358                    } else if (r instanceof CurriculumReservation) {
359                        reservationEl.addAttribute("type", "curriculum");
360                        CurriculumReservation cr = (CurriculumReservation)r;
361                        if (cr.getReservationLimit() >= 0.0)
362                            reservationEl.addAttribute("limit", String.valueOf(cr.getReservationLimit()));
363                        reservationEl.addAttribute("area", cr.getAcademicArea());
364                        for (String clasf: cr.getClassifications())
365                            reservationEl.addElement("classification").addAttribute("code", clasf);
366                        for (String major: cr.getMajors())
367                            reservationEl.addElement("major").addAttribute("code", major);
368                    } else if (r instanceof CourseReservation) {
369                        reservationEl.addAttribute("type", "course");
370                        CourseReservation cr = (CourseReservation)r;
371                        reservationEl.addAttribute("course", getId("course",cr.getCourse().getId()));
372                    } else if (r instanceof DummyReservation) {
373                        reservationEl.addAttribute("type", "dummy");
374                    }
375                    reservationEl.addAttribute("priority", String.valueOf(r.getPriority()));
376                    if (r.mustBeUsed()) reservationEl.addAttribute("mustBeUsed", "true");
377                    if (r.isAllowOverlap()) reservationEl.addAttribute("allowOverlap", "true");
378                    if (r.canAssignOverLimit()) reservationEl.addAttribute("canAssignOverLimit", "true");
379                    for (Config config: r.getConfigs())
380                        reservationEl.addElement("config").addAttribute("id", getId("config", config.getId()));
381                    for (Map.Entry<Subpart, Set<Section>> entry: r.getSections().entrySet()) {
382                        for (Section section: entry.getValue()) {
383                            reservationEl.addElement("section").addAttribute("id", getId("section", section.getId()));
384                        }
385                    }
386                }
387            }
388        }
389
390        Element studentsEl = root.addElement("students");
391        for (Student student : getModel().getStudents()) {
392            Element studentEl = studentsEl.addElement("student");
393            studentEl.addAttribute("id", getId("student", student.getId()));
394            if (iShowNames) {
395                if (student.getExternalId() != null && !student.getExternalId().isEmpty())
396                    studentEl.addAttribute("externalId", student.getExternalId());
397                if (student.getName() != null && !student.getName().isEmpty())
398                    studentEl.addAttribute("name", student.getName());
399                if (student.getStatus() != null && !student.getStatus().isEmpty())
400                    studentEl.addAttribute("status", student.getStatus());
401            }
402            if (student.isDummy())
403                studentEl.addAttribute("dummy", "true");
404            if (iSaveStudentInfo) {
405                for (AcademicAreaCode aac : student.getAcademicAreaClasiffications()) {
406                    Element aacEl = studentEl.addElement("classification");
407                    if (aac.getArea() != null)
408                        aacEl.addAttribute("area", aac.getArea());
409                    if (aac.getCode() != null)
410                        aacEl.addAttribute("code", aac.getCode());
411                }
412                for (AcademicAreaCode aac : student.getMajors()) {
413                    Element aacEl = studentEl.addElement("major");
414                    if (aac.getArea() != null)
415                        aacEl.addAttribute("area", aac.getArea());
416                    if (aac.getCode() != null)
417                        aacEl.addAttribute("code", aac.getCode());
418                }
419                for (AcademicAreaCode aac : student.getMinors()) {
420                    Element aacEl = studentEl.addElement("minor");
421                    if (aac.getArea() != null)
422                        aacEl.addAttribute("area", aac.getArea());
423                    if (aac.getCode() != null)
424                        aacEl.addAttribute("code", aac.getCode());
425                }
426            }
427            for (Request request : student.getRequests()) {
428                if (request instanceof FreeTimeRequest) {
429                    Element requestEl = studentEl.addElement("freeTime");
430                    FreeTimeRequest ft = (FreeTimeRequest) request;
431                    requestEl.addAttribute("id", getId("request", request.getId()));
432                    requestEl.addAttribute("priority", String.valueOf(request.getPriority()));
433                    if (request.isAlternative())
434                        requestEl.addAttribute("alternative", "true");
435                    if (request.getWeight() != 1.0)
436                        requestEl.addAttribute("weight", sStudentWeightFormat.format(request.getWeight()));
437                    TimeLocation tl = ft.getTime();
438                    if (tl != null) {
439                        requestEl.addAttribute("days", sDF[7].format(Long.parseLong(Integer.toBinaryString(tl
440                                .getDayCode()))));
441                        requestEl.addAttribute("start", String.valueOf(tl.getStartSlot()));
442                        requestEl.addAttribute("length", String.valueOf(tl.getLength()));
443                        if (iShowNames && tl.getDatePatternId() != null)
444                            requestEl.addAttribute("datePattern", tl.getDatePatternId().toString());
445                        requestEl.addAttribute("dates", bitset2string(tl.getWeekCode()));
446                        if (iShowNames)
447                            requestEl.setText(tl.getLongName(true));
448                    }
449                    if (iSaveInitial && request.getInitialAssignment() != null) {
450                        requestEl.addElement("initial");
451                    }
452                    if (iSaveCurrent && getAssignment().getValue(request) != null) {
453                        requestEl.addElement("current");
454                    }
455                    if (iSaveBest && request.getBestAssignment() != null) {
456                        requestEl.addElement("best");
457                    }
458                } else if (request instanceof CourseRequest) {
459                    CourseRequest cr = (CourseRequest) request;
460                    Element requestEl = studentEl.addElement("course");
461                    requestEl.addAttribute("id", getId("request", request.getId()));
462                    requestEl.addAttribute("priority", String.valueOf(request.getPriority()));
463                    if (request.isAlternative())
464                        requestEl.addAttribute("alternative", "true");
465                    if (request.getWeight() != 1.0)
466                        requestEl.addAttribute("weight", sStudentWeightFormat.format(request.getWeight()));
467                    requestEl.addAttribute("waitlist", cr.isWaitlist() ? "true" : "false");
468                    if (cr.getTimeStamp() != null)
469                        requestEl.addAttribute("timeStamp", cr.getTimeStamp().toString());
470                    boolean first = true;
471                    for (Course course : cr.getCourses()) {
472                        if (first)
473                            requestEl.addAttribute("course", getId("course", course.getId()));
474                        else
475                            requestEl.addElement("alternative").addAttribute("course", getId("course", course.getId()));
476                        first = false;
477                    }
478                    for (Choice choice : cr.getWaitlistedChoices()) {
479                        Element choiceEl = requestEl.addElement("waitlisted");
480                        choiceEl.addAttribute("offering", getId("offering", choice.getOffering().getId()));
481                        choiceEl.setText(choice.getId());
482                    }
483                    for (Choice choice : cr.getSelectedChoices()) {
484                        Element choiceEl = requestEl.addElement("selected");
485                        choiceEl.addAttribute("offering", getId("offering", choice.getOffering().getId()));
486                        choiceEl.setText(choice.getId());
487                    }
488                    if (iSaveInitial && request.getInitialAssignment() != null) {
489                        Element assignmentEl = requestEl.addElement("initial");
490                        Enrollment enrollment = request.getInitialAssignment();
491                        if (enrollment.getReservation() != null)
492                            assignmentEl.addAttribute("reservation", getId("reservation", enrollment.getReservation().getId()));
493                        for (Section section : enrollment.getSections()) {
494                            Element sectionEl = assignmentEl.addElement("section").addAttribute("id",
495                                    getId("section", section.getId()));
496                            if (iShowNames)
497                                sectionEl.setText(section.getName()
498                                        + " "
499                                        + (section.getTime() == null ? " Arr Hrs" : " "
500                                                + section.getTime().getLongName(true))
501                                        + (section.getNrRooms() == 0 ? "" : " "
502                                                + section.getPlacement().getRoomName(","))
503                                        + (section.getChoice().getInstructorNames() == null ? "" : " "
504                                                + section.getChoice().getInstructorNames()));
505                        }
506                    }
507                    if (iSaveCurrent && getAssignment().getValue(request) != null) {
508                        Element assignmentEl = requestEl.addElement("current");
509                        Enrollment enrollment = getAssignment().getValue(request);
510                        if (enrollment.getReservation() != null)
511                            assignmentEl.addAttribute("reservation", getId("reservation", enrollment.getReservation().getId()));
512                        for (Section section : enrollment.getSections()) {
513                            Element sectionEl = assignmentEl.addElement("section").addAttribute("id",
514                                    getId("section", section.getId()));
515                            if (iShowNames)
516                                sectionEl.setText(section.getName()
517                                        + " "
518                                        + (section.getTime() == null ? " Arr Hrs" : " "
519                                                + section.getTime().getLongName(true))
520                                        + (section.getNrRooms() == 0 ? "" : " "
521                                                + section.getPlacement().getRoomName(","))
522                                        + (section.getChoice().getInstructorNames() == null ? "" : " "
523                                                + section.getChoice().getInstructorNames()));
524                        }
525                    }
526                    if (iSaveBest && request.getBestAssignment() != null) {
527                        Element assignmentEl = requestEl.addElement("best");
528                        Enrollment enrollment = request.getBestAssignment();
529                        if (enrollment.getReservation() != null)
530                            assignmentEl.addAttribute("reservation", getId("reservation", enrollment.getReservation().getId()));
531                        for (Section section : enrollment.getSections()) {
532                            Element sectionEl = assignmentEl.addElement("section").addAttribute("id",
533                                    getId("section", section.getId()));
534                            if (iShowNames)
535                                sectionEl.setText(section.getName()
536                                        + " "
537                                        + (section.getTime() == null ? " Arr Hrs" : " "
538                                                + section.getTime().getLongName(true))
539                                        + (section.getNrRooms() == 0 ? "" : " "
540                                                + section.getPlacement().getRoomName(","))
541                                        + (section.getChoice().getInstructorNames() == null ? "" : " "
542                                                + section.getChoice().getInstructorNames()));
543                        }
544                    }
545                }
546            }
547        }
548        
549        Element constrainstEl = root.addElement("constraints");
550        for (LinkedSections linkedSections: getModel().getLinkedSections()) {
551            Element linkEl = constrainstEl.addElement("linked-sections");
552            for (Offering offering: linkedSections.getOfferings())
553                for (Subpart subpart: linkedSections.getSubparts(offering))
554                    for (Section section: linkedSections.getSections(subpart))
555                        linkEl.addElement("section")
556                            .addAttribute("offering", getId("offering", offering.getId()))
557                            .addAttribute("id", getId("section", section.getId()));
558        }
559        
560        if (getModel().getDistanceConflict() != null) {
561            Map<Long, Map<Long, Integer>> travelTimes = getModel().getDistanceConflict().getDistanceMetric().getTravelTimes();
562            if (travelTimes != null) {
563                Element travelTimesEl = root.addElement("travel-times");
564                for (Map.Entry<Long, Map<Long, Integer>> e1: travelTimes.entrySet())
565                    for (Map.Entry<Long, Integer> e2: e1.getValue().entrySet())
566                        travelTimesEl.addElement("travel-time")
567                            .addAttribute("id1", getId("room", e1.getKey().toString()))
568                            .addAttribute("id2", getId("room", e2.getKey().toString()))
569                            .addAttribute("minutes", e2.getValue().toString());
570            }
571        }
572
573
574        if (iShowNames) {
575            Progress.getInstance(getModel()).save(root);
576        }
577
578        FileOutputStream fos = null;
579        try {
580            fos = new FileOutputStream(outFile);
581            (new XMLWriter(fos, OutputFormat.createPrettyPrint())).write(document);
582            fos.flush();
583            fos.close();
584            fos = null;
585        } finally {
586            try {
587                if (fos != null)
588                    fos.close();
589            } catch (IOException e) {
590            }
591        }
592
593        if (iConvertIds)
594            IdConvertor.getInstance().save();
595    }
596
597}