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.AreaClassificationMajor;
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.Instructor;
030import org.cpsolver.studentsct.model.Offering;
031import org.cpsolver.studentsct.model.Request;
032import org.cpsolver.studentsct.model.Request.RequestPriority;
033import org.cpsolver.studentsct.model.RequestGroup;
034import org.cpsolver.studentsct.model.Section;
035import org.cpsolver.studentsct.model.Student;
036import org.cpsolver.studentsct.model.Student.StudentPriority;
037import org.cpsolver.studentsct.model.StudentGroup;
038import org.cpsolver.studentsct.model.Subpart;
039import org.cpsolver.studentsct.model.Unavailability;
040import org.cpsolver.studentsct.reservation.CourseReservation;
041import org.cpsolver.studentsct.reservation.CourseRestriction;
042import org.cpsolver.studentsct.reservation.CurriculumOverride;
043import org.cpsolver.studentsct.reservation.CurriculumReservation;
044import org.cpsolver.studentsct.reservation.CurriculumRestriction;
045import org.cpsolver.studentsct.reservation.DummyReservation;
046import org.cpsolver.studentsct.reservation.GroupReservation;
047import org.cpsolver.studentsct.reservation.IndividualReservation;
048import org.cpsolver.studentsct.reservation.IndividualRestriction;
049import org.cpsolver.studentsct.reservation.LearningCommunityReservation;
050import org.cpsolver.studentsct.reservation.Reservation;
051import org.cpsolver.studentsct.reservation.ReservationOverride;
052import org.cpsolver.studentsct.reservation.Restriction;
053import org.dom4j.Document;
054import org.dom4j.DocumentHelper;
055import org.dom4j.Element;
056import org.dom4j.io.OutputFormat;
057import org.dom4j.io.XMLWriter;
058
059
060/**
061 * Save student sectioning solution into an XML file.
062 * 
063 * <br>
064 * <br>
065 * Parameters:
066 * <table border='1' summary='Related Solver Parameters'>
067 * <tr>
068 * <th>Parameter</th>
069 * <th>Type</th>
070 * <th>Comment</th>
071 * </tr>
072 * <tr>
073 * <td>General.Output</td>
074 * <td>{@link String}</td>
075 * <td>Folder with the output solution in XML format (solution.xml)</td>
076 * </tr>
077 * <tr>
078 * <td>Xml.ConvertIds</td>
079 * <td>{@link Boolean}</td>
080 * <td>If true, ids are converted (to be able to make input data public)</td>
081 * </tr>
082 * <tr>
083 * <td>Xml.ShowNames</td>
084 * <td>{@link Boolean}</td>
085 * <td>If false, names are not exported (to be able to make input data public)</td>
086 * </tr>
087 * <tr>
088 * <td>Xml.SaveBest</td>
089 * <td>{@link Boolean}</td>
090 * <td>If true, best solution is saved.</td>
091 * </tr>
092 * <tr>
093 * <td>Xml.SaveInitial</td>
094 * <td>{@link Boolean}</td>
095 * <td>If true, initial solution is saved.</td>
096 * </tr>
097 * <tr>
098 * <td>Xml.SaveCurrent</td>
099 * <td>{@link Boolean}</td>
100 * <td>If true, current solution is saved.</td>
101 * </tr>
102 * <tr>
103 * <td>Xml.SaveOnlineSectioningInfo</td>
104 * <td>{@link Boolean}</td>
105 * <td>If true, save online sectioning info (i.e., expected and held space of
106 * each section)</td>
107 * </tr>
108 * <tr>
109 * <td>Xml.SaveStudentInfo</td>
110 * <td>{@link Boolean}</td>
111 * <td>If true, save student information (i.e., academic area classification,
112 * major, minor)</td>
113 * </tr>
114 * </table>
115 * <br>
116 * <br>
117 * Usage:
118 * <pre><code>
119 * new StudentSectioningXMLSaver(solver).save(new File("solution.xml")); 
120 * </code></pre>
121 * 
122 * @version StudentSct 1.3 (Student Sectioning)<br>
123 *          Copyright (C) 2007 - 2014 Tomáš Müller<br>
124 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
125 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
126 * <br>
127 *          This library is free software; you can redistribute it and/or modify
128 *          it under the terms of the GNU Lesser General Public License as
129 *          published by the Free Software Foundation; either version 3 of the
130 *          License, or (at your option) any later version. <br>
131 * <br>
132 *          This library is distributed in the hope that it will be useful, but
133 *          WITHOUT ANY WARRANTY; without even the implied warranty of
134 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
135 *          Lesser General Public License for more details. <br>
136 * <br>
137 *          You should have received a copy of the GNU Lesser General Public
138 *          License along with this library; if not see
139 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
140 */
141
142public class StudentSectioningXMLSaver extends StudentSectioningSaver {
143    private static org.apache.logging.log4j.Logger sLogger = org.apache.logging.log4j.LogManager.getLogger(StudentSectioningXMLSaver.class);
144    private static DecimalFormat[] sDF = { new DecimalFormat(""), new DecimalFormat("0"), new DecimalFormat("00"),
145            new DecimalFormat("000"), new DecimalFormat("0000"), new DecimalFormat("00000"),
146            new DecimalFormat("000000"), new DecimalFormat("0000000") };
147    private static DecimalFormat sStudentWeightFormat = new DecimalFormat("0.0000", new DecimalFormatSymbols(Locale.US));
148    private File iOutputFolder = null;
149
150    private boolean iSaveBest = false;
151    private boolean iSaveInitial = false;
152    private boolean iSaveCurrent = false;
153    private boolean iSaveOnlineSectioningInfo = false;
154    private boolean iSaveStudentInfo = true;
155
156    private boolean iConvertIds = false;
157    private boolean iShowNames = false;
158    
159    static {
160        sStudentWeightFormat.setRoundingMode(RoundingMode.DOWN);
161    }
162
163    /**
164     * Constructor
165     * 
166     * @param solver
167     *            student sectioning solver
168     */
169    public StudentSectioningXMLSaver(Solver<Request, Enrollment> solver) {
170        super(solver);
171        iOutputFolder = new File(getModel().getProperties().getProperty("General.Output",
172                "." + File.separator + "output"));
173        iSaveBest = getModel().getProperties().getPropertyBoolean("Xml.SaveBest", true);
174        iSaveInitial = getModel().getProperties().getPropertyBoolean("Xml.SaveInitial", true);
175        iSaveCurrent = getModel().getProperties().getPropertyBoolean("Xml.SaveCurrent", false);
176        iSaveOnlineSectioningInfo = getModel().getProperties().getPropertyBoolean("Xml.SaveOnlineSectioningInfo", true);
177        iSaveStudentInfo = getModel().getProperties().getPropertyBoolean("Xml.SaveStudentInfo", true);
178        iShowNames = getModel().getProperties().getPropertyBoolean("Xml.ShowNames", true);
179        iConvertIds = getModel().getProperties().getPropertyBoolean("Xml.ConvertIds", false);
180    }
181
182    /** Convert bitset to a bit string */
183    private static String bitset2string(BitSet b) {
184        StringBuffer sb = new StringBuffer();
185        for (int i = 0; i < b.length(); i++)
186            sb.append(b.get(i) ? "1" : "0");
187        return sb.toString();
188    }
189
190    /** Generate id for given object with the given id */
191    private String getId(String type, String id) {
192        if (!iConvertIds)
193            return id.toString();
194        return IdConvertor.getInstance().convert(type, id);
195    }
196
197    /** Generate id for given object with the given id */
198    private String getId(String type, Number id) {
199        return getId(type, id.toString());
200    }
201
202    /** Generate id for given object with the given id */
203    private String getId(String type, long id) {
204        return getId(type, String.valueOf(id));
205    }
206
207    /** Save an XML file */
208    @Override
209    public void save() throws Exception {
210        save(null);
211    }
212
213    /**
214     * Save an XML file
215     * 
216     * @param outFile
217     *            output file
218     * @throws Exception thrown when the save fails
219     */
220    public void save(File outFile) throws Exception {
221        if (outFile == null) {
222            outFile = new File(iOutputFolder, "solution.xml");
223        } else if (outFile.getParentFile() != null) {
224            outFile.getParentFile().mkdirs();
225        }
226        sLogger.debug("Writting XML data to:" + outFile);
227
228        Document document = DocumentHelper.createDocument();
229        document.addComment("Student Sectioning");
230        
231        populate(document);
232
233        FileOutputStream fos = null;
234        try {
235            fos = new FileOutputStream(outFile);
236            (new XMLWriter(fos, OutputFormat.createPrettyPrint())).write(document);
237            fos.flush();
238            fos.close();
239            fos = null;
240        } finally {
241            try {
242                if (fos != null)
243                    fos.close();
244            } catch (IOException e) {
245            }
246        }
247
248        if (iConvertIds)
249            IdConvertor.getInstance().save();
250    }
251    
252    public Document saveDocument() {
253        Document document = DocumentHelper.createDocument();
254        document.addComment("Student Sectioning");
255        
256        populate(document);
257
258        return document;
259    }
260
261    /**
262     * Fill in all the data into the given document
263     * @param document document to be populated
264     */
265    protected void populate(Document document) {
266        if (iSaveCurrent || iSaveBest) {
267            StringBuffer comments = new StringBuffer("Solution Info:\n");
268            Map<String, String> solutionInfo = (getSolution() == null ? getModel().getExtendedInfo(getAssignment()) : getSolution().getExtendedInfo());
269            for (String key : new TreeSet<String>(solutionInfo.keySet())) {
270                String value = solutionInfo.get(key);
271                comments.append("    " + key + ": " + value + "\n");
272            }
273            document.addComment(comments.toString());
274        }
275
276        Element root = document.addElement("sectioning");
277        root.addAttribute("version", "1.0");
278        root.addAttribute("initiative", getModel().getProperties().getProperty("Data.Initiative"));
279        root.addAttribute("term", getModel().getProperties().getProperty("Data.Term"));
280        root.addAttribute("year", getModel().getProperties().getProperty("Data.Year"));
281        root.addAttribute("created", String.valueOf(new Date()));
282
283        saveOfferings(root);
284
285        saveStudents(root);
286        
287        saveLinkedSections(root);
288        
289        saveTravelTimes(root);
290
291        if (iShowNames) {
292            Progress.getInstance(getModel()).save(root);
293        }
294    }
295    
296    /**
297     * Save offerings
298     * @param root document root
299     */
300    protected void saveOfferings(Element root) {
301        Element offeringsEl = root.addElement("offerings");
302        for (Offering offering : getModel().getOfferings()) {
303            Element offeringEl = offeringsEl.addElement("offering");
304            saveOffering(offeringEl, offering);
305            saveReservations(offeringEl, offering);
306            saveRestrictions(offeringEl, offering);
307        }
308    }
309    
310    /**
311     * Save given offering
312     * @param offeringEl offering element to be populated
313     * @param offering offering to be saved
314     */
315    protected void saveOffering(Element offeringEl, Offering offering) {
316        offeringEl.addAttribute("id", getId("offering", offering.getId()));
317        if (iShowNames)
318            offeringEl.addAttribute("name", offering.getName());
319        for (Course course : offering.getCourses()) {
320            Element courseEl = offeringEl.addElement("course");
321            saveCourse(courseEl, course);
322        }
323        for (Config config : offering.getConfigs()) {
324            Element configEl = offeringEl.addElement("config");
325            saveConfig(configEl, config);
326        }
327    }
328    
329    /**
330     * Save given course
331     * @param courseEl course element to be populated
332     * @param course course to be saved
333     */
334    protected void saveCourse(Element courseEl, Course course) {
335        courseEl.addAttribute("id", getId("course", course.getId()));
336        if (iShowNames)
337            courseEl.addAttribute("subjectArea", course.getSubjectArea());
338        if (iShowNames)
339            courseEl.addAttribute("courseNbr", course.getCourseNumber());
340        if (iShowNames && course.getLimit() >= 0)
341            courseEl.addAttribute("limit", String.valueOf(course.getLimit()));
342        if (iShowNames && course.getProjected() != 0)
343            courseEl.addAttribute("projected", String.valueOf(course.getProjected()));
344        if (iShowNames && course.getCredit() != null)
345            courseEl.addAttribute("credit", course.getCredit());
346        if (course.hasCreditValue())
347            courseEl.addAttribute("credits", course.getCreditValue().toString());
348    }
349    
350    /**
351     * Save given config
352     * @param configEl config element to be populated
353     * @param config config to be saved
354     */
355    protected void saveConfig(Element configEl, Config config) {
356        configEl.addAttribute("id", getId("config", config.getId()));
357        if (config.getLimit() >= 0)
358            configEl.addAttribute("limit", String.valueOf(config.getLimit()));
359        if (iShowNames)
360            configEl.addAttribute("name", config.getName());
361        for (Subpart subpart : config.getSubparts()) {
362            Element subpartEl = configEl.addElement("subpart");
363            saveSubpart(subpartEl, subpart);
364        }
365        if (config.getInstructionalMethodId() != null) {
366            Element imEl = configEl.addElement("instructional-method");
367            imEl.addAttribute("id", getId("instructional-method", config.getInstructionalMethodId()));
368            if (iShowNames && config.getInstructionalMethodName() != null)
369                imEl.addAttribute("name", config.getInstructionalMethodName());
370            if (iShowNames && config.getInstructionalMethodReference() != null)
371                imEl.addAttribute("reference", config.getInstructionalMethodReference());
372        }
373    }
374    
375    /**
376     * Save scheduling subpart
377     * @param subpartEl subpart element to be populated
378     * @param subpart subpart to be saved
379     */
380    protected void saveSubpart(Element subpartEl, Subpart subpart) {
381        subpartEl.addAttribute("id", getId("subpart", subpart.getId()));
382        subpartEl.addAttribute("itype", subpart.getInstructionalType());
383        if (subpart.getParent() != null)
384            subpartEl.addAttribute("parent", getId("subpart", subpart.getParent().getId()));
385        if (iShowNames) {
386            subpartEl.addAttribute("name", subpart.getName());
387            if (subpart.getCredit() != null)
388                subpartEl.addAttribute("credit", subpart.getCredit());
389            if (subpart.hasCreditValue())
390                subpartEl.addAttribute("credits", subpart.getCreditValue().toString());
391        }
392        if (subpart.isAllowOverlap())
393            subpartEl.addAttribute("allowOverlap", "true");
394        for (Section section : subpart.getSections()) {
395            Element sectionEl = subpartEl.addElement("section");
396            saveSection(sectionEl, section);
397        }
398    }
399    
400    /**
401     * Save section
402     * @param sectionEl section element to be populated
403     * @param section section to be saved
404     */
405    protected void saveSection(Element sectionEl, Section section) {
406        sectionEl.addAttribute("id", getId("section", section.getId()));
407        sectionEl.addAttribute("limit", String.valueOf(section.getLimit()));
408        if (section.isCancelled())
409            sectionEl.addAttribute("cancelled", "true");
410        if (!section.isEnabled())
411            sectionEl.addAttribute("enabled", "false");
412        if (section.isOnline())
413            sectionEl.addAttribute("online", "true");
414        if (section.isPast())
415            sectionEl.addAttribute("past", "true");
416        if (iShowNames && section.getNameByCourse() != null)
417            for (Map.Entry<Long, String> entry: section.getNameByCourse().entrySet())
418                sectionEl.addElement("cname").addAttribute("id", getId("course", entry.getKey())).setText(entry.getValue());
419        if (section.getParent() != null)
420            sectionEl.addAttribute("parent", getId("section", section.getParent().getId()));
421        if (section.hasInstructors()) {
422            for (Instructor instructor: section.getInstructors()) {
423                Element instructorEl = sectionEl.addElement("instructor");
424                instructorEl.addAttribute("id", getId("instructor", instructor.getId()));
425                if (iShowNames && instructor.getName() != null)
426                    instructorEl.addAttribute("name", instructor.getName());
427                if (iShowNames && instructor.getExternalId() != null)
428                    instructorEl.addAttribute("externalId", instructor.getExternalId());
429                if (iShowNames && instructor.getEmail() != null)
430                    instructorEl.addAttribute("email", instructor.getExternalId());
431            }
432        }
433        if (iShowNames)
434            sectionEl.addAttribute("name", section.getName());
435        if (section.getPlacement() != null) {
436            TimeLocation tl = section.getPlacement().getTimeLocation();
437            if (tl != null) {
438                Element timeLocationEl = sectionEl.addElement("time");
439                timeLocationEl.addAttribute("days", sDF[7].format(Long.parseLong(Integer
440                        .toBinaryString(tl.getDayCode()))));
441                timeLocationEl.addAttribute("start", String.valueOf(tl.getStartSlot()));
442                timeLocationEl.addAttribute("length", String.valueOf(tl.getLength()));
443                if (tl.getBreakTime() != 0)
444                    timeLocationEl.addAttribute("breakTime", String.valueOf(tl.getBreakTime()));
445                if (iShowNames && tl.getTimePatternId() != null)
446                    timeLocationEl.addAttribute("pattern", getId("timePattern", tl.getTimePatternId()));
447                if (iShowNames && tl.getDatePatternId() != null)
448                    timeLocationEl.addAttribute("datePattern", tl.getDatePatternId().toString());
449                if (iShowNames && tl.getDatePatternName() != null
450                        && tl.getDatePatternName().length() > 0)
451                    timeLocationEl.addAttribute("datePatternName", tl.getDatePatternName());
452                timeLocationEl.addAttribute("dates", bitset2string(tl.getWeekCode()));
453                if (iShowNames)
454                    timeLocationEl.setText(tl.getLongName(true));
455            }
456            for (RoomLocation rl : section.getRooms()) {
457                Element roomLocationEl = sectionEl.addElement("room");
458                roomLocationEl.addAttribute("id", getId("room", rl.getId()));
459                if (iShowNames && rl.getBuildingId() != null)
460                    roomLocationEl.addAttribute("building", getId("building", rl.getBuildingId()));
461                if (iShowNames && rl.getName() != null)
462                    roomLocationEl.addAttribute("name", rl.getName());
463                roomLocationEl.addAttribute("capacity", String.valueOf(rl.getRoomSize()));
464                if (rl.getPosX() != null && rl.getPosY() != null)
465                    roomLocationEl.addAttribute("location", rl.getPosX() + "," + rl.getPosY());
466                if (rl.getIgnoreTooFar())
467                    roomLocationEl.addAttribute("ignoreTooFar", "true");
468            }
469        }
470        if (iSaveOnlineSectioningInfo) {
471            if (section.getSpaceHeld() != 0.0)
472                sectionEl.addAttribute("hold", sStudentWeightFormat.format(section.getSpaceHeld()));
473            if (section.getSpaceExpected() != 0.0)
474                sectionEl.addAttribute("expect", sStudentWeightFormat
475                        .format(section.getSpaceExpected()));
476        }
477        if (section.getIgnoreConflictWithSectionIds() != null && !section.getIgnoreConflictWithSectionIds().isEmpty()) {
478            Element ignoreEl = sectionEl.addElement("no-conflicts");
479            for (Long sectionId: section.getIgnoreConflictWithSectionIds())
480                ignoreEl.addElement("section").addAttribute("id", getId("section", sectionId));
481        }
482    }
483    
484    /**
485     * Save reservations of the given offering
486     * @param offeringEl offering element to be populated with reservations
487     * @param offering offering which reservations are to be saved
488     */
489    protected void saveReservations(Element offeringEl, Offering offering) {
490        if (!offering.getReservations().isEmpty()) {
491            for (Reservation r: offering.getReservations()) {
492                saveReservation(offeringEl.addElement("reservation"), r);
493            }
494        }
495    }
496    
497    /**
498     * Save reservation
499     * @param reservationEl reservation element to be populated
500     * @param reservation reservation to be saved
501     */
502    protected void saveReservation(Element reservationEl, Reservation reservation) {
503        reservationEl.addAttribute("id", getId("reservation", reservation.getId()));
504        reservationEl.addAttribute("expired", reservation.isExpired() ? "true" : "false");
505        if (reservation instanceof LearningCommunityReservation) {
506            LearningCommunityReservation lc = (LearningCommunityReservation)reservation;
507            reservationEl.addAttribute("type", "lc");
508            for (Long studentId: lc.getStudentIds())
509                reservationEl.addElement("student").addAttribute("id", getId("student", studentId));
510            if (lc.getReservationLimit() >= 0.0)
511                reservationEl.addAttribute("limit", String.valueOf(lc.getReservationLimit()));
512            reservationEl.addAttribute("course", getId("course",lc.getCourse().getId()));
513        } else if (reservation instanceof GroupReservation) {
514            GroupReservation gr = (GroupReservation)reservation;
515            reservationEl.addAttribute("type", "group");
516            for (Long studentId: gr.getStudentIds())
517                reservationEl.addElement("student").addAttribute("id", getId("student", studentId));
518            if (gr.getReservationLimit() >= 0.0)
519                reservationEl.addAttribute("limit", String.valueOf(gr.getReservationLimit()));
520        } else if (reservation instanceof ReservationOverride) {
521            reservationEl.addAttribute("type", "override");
522            ReservationOverride o = (ReservationOverride)reservation;
523            for (Long studentId: o.getStudentIds())
524                reservationEl.addElement("student").addAttribute("id", getId("student", studentId));
525        } else if (reservation instanceof IndividualReservation) {
526            reservationEl.addAttribute("type", "individual");
527            for (Long studentId: ((IndividualReservation)reservation).getStudentIds())
528                reservationEl.addElement("student").addAttribute("id", getId("student", studentId));
529        } else if (reservation instanceof CurriculumReservation) {
530            reservationEl.addAttribute("type", (reservation instanceof CurriculumOverride ? "curriculum-override" : "curriculum"));
531            CurriculumReservation cr = (CurriculumReservation)reservation;
532            if (cr.getReservationLimit() >= 0.0)
533                reservationEl.addAttribute("limit", String.valueOf(cr.getReservationLimit()));
534            if (cr.getAcademicAreas().size() == 1)
535                reservationEl.addAttribute("area", cr.getAcademicAreas().iterator().next());
536            else {
537                for (String area: cr.getAcademicAreas())
538                    reservationEl.addElement("area").addAttribute("code", area);
539            }
540            for (String clasf: cr.getClassifications())
541                reservationEl.addElement("classification").addAttribute("code", clasf);
542            for (String major: cr.getMajors()) {
543                Element majorEl = reservationEl.addElement("major").addAttribute("code", major);
544                Set<String> concentrations = cr.getConcentrations(major);
545                if (concentrations != null)
546                    for (String conc: concentrations)
547                        majorEl.addElement("concentration").addAttribute("code", conc);
548            }
549            for (String minor: cr.getMinors())
550                reservationEl.addElement("minor").addAttribute("code", minor);
551        } else if (reservation instanceof CourseReservation) {
552            reservationEl.addAttribute("type", "course");
553            CourseReservation cr = (CourseReservation)reservation;
554            reservationEl.addAttribute("course", getId("course",cr.getCourse().getId()));
555        } else if (reservation instanceof DummyReservation) {
556            reservationEl.addAttribute("type", "dummy");
557        }
558        reservationEl.addAttribute("priority", String.valueOf(reservation.getPriority()));
559        reservationEl.addAttribute("mustBeUsed", reservation.mustBeUsed() ? "true" : "false");
560        reservationEl.addAttribute("allowOverlap", reservation.isAllowOverlap() ? "true" : "false");
561        reservationEl.addAttribute("canAssignOverLimit", reservation.canAssignOverLimit() ? "true" : "false");
562        reservationEl.addAttribute("allowDisabled", reservation.isAllowDisabled() ? "true" : "false");
563        if (reservation.neverIncluded()) reservationEl.addAttribute("neverIncluded", "true");
564        if (reservation.canBreakLinkedSections()) reservationEl.addAttribute("breakLinkedSections", "true");
565        for (Config config: reservation.getConfigs())
566            reservationEl.addElement("config").addAttribute("id", getId("config", config.getId()));
567        for (Map.Entry<Subpart, Set<Section>> entry: reservation.getSections().entrySet()) {
568            for (Section section: entry.getValue()) {
569                reservationEl.addElement("section").addAttribute("id", getId("section", section.getId()));
570            }
571        }
572    }
573    
574    /**
575     * Save restrictions of the given offering
576     * @param offeringEl offering element to be populated with restrictions
577     * @param offering offering which restrictions are to be saved
578     */
579    protected void saveRestrictions(Element offeringEl, Offering offering) {
580        if (!offering.getRestrictions().isEmpty()) {
581            for (Restriction r: offering.getRestrictions()) {
582                saveRestriction(offeringEl.addElement("restriction"), r);
583            }
584        }
585    }
586    
587    /**
588     * Save restriction
589     * @param restrictionEl restriction element to be populated
590     * @param restriction restriction to be saved
591     */
592    protected void saveRestriction(Element restrictionEl, Restriction restriction) {
593        restrictionEl.addAttribute("id", getId("restriction", restriction.getId()));
594        if (restriction instanceof IndividualRestriction) {
595            restrictionEl.addAttribute("type", "individual");
596            for (Long studentId: ((IndividualRestriction)restriction).getStudentIds())
597                restrictionEl.addElement("student").addAttribute("id", getId("student", studentId));
598        } else if (restriction instanceof CurriculumRestriction) {
599            restrictionEl.addAttribute("type", "curriculum");
600            CurriculumRestriction cr = (CurriculumRestriction)restriction;
601            if (cr.getAcademicAreas().size() == 1)
602                restrictionEl.addAttribute("area", cr.getAcademicAreas().iterator().next());
603            else {
604                for (String area: cr.getAcademicAreas())
605                    restrictionEl.addElement("area").addAttribute("code", area);
606            }
607            for (String clasf: cr.getClassifications())
608                restrictionEl.addElement("classification").addAttribute("code", clasf);
609            for (String major: cr.getMajors()) {
610                Element majorEl = restrictionEl.addElement("major").addAttribute("code", major);
611                Set<String> concentrations = cr.getConcentrations(major);
612                if (concentrations != null)
613                    for (String conc: concentrations)
614                        majorEl.addElement("concentration").addAttribute("code", conc);
615            }
616            for (String minor: cr.getMinors())
617                restrictionEl.addElement("minor").addAttribute("code", minor);
618        } else if (restriction instanceof CourseRestriction) {
619            restrictionEl.addAttribute("type", "course");
620            CourseRestriction cr = (CourseRestriction)restriction;
621            restrictionEl.addAttribute("course", getId("course",cr.getCourse().getId()));
622        }
623        for (Config config: restriction.getConfigs())
624            restrictionEl.addElement("config").addAttribute("id", getId("config", config.getId()));
625        for (Map.Entry<Subpart, Set<Section>> entry: restriction.getSections().entrySet()) {
626            for (Section section: entry.getValue()) {
627                restrictionEl.addElement("section").addAttribute("id", getId("section", section.getId()));
628            }
629        }
630    }
631    
632    /**
633     * Save students
634     * @param root document root
635     */
636    protected void saveStudents(Element root) {
637        Element studentsEl = root.addElement("students");
638        for (Student student : getModel().getStudents()) {
639            Element studentEl = studentsEl.addElement("student");
640            saveStudent(studentEl, student);
641            for (Request request : student.getRequests()) {
642                saveRequest(studentEl, request);
643            }
644        }
645    }
646    
647    /**
648     * Save student
649     * @param studentEl student element to be populated
650     * @param student student to be saved
651     */
652    protected void saveStudent(Element studentEl, Student student) {
653        studentEl.addAttribute("id", getId("student", student.getId()));
654        if (iShowNames) {
655            if (student.getExternalId() != null && !student.getExternalId().isEmpty())
656                studentEl.addAttribute("externalId", student.getExternalId());
657            if (student.getName() != null && !student.getName().isEmpty())
658                studentEl.addAttribute("name", student.getName());
659            if (student.getStatus() != null && !student.getStatus().isEmpty())
660                studentEl.addAttribute("status", student.getStatus());
661        }
662        if (student.isDummy())
663            studentEl.addAttribute("dummy", "true");
664        if (student.getPriority().ordinal() < StudentPriority.Normal.ordinal())
665            studentEl.addAttribute("priority", student.getPriority().name());
666        if (student.isNeedShortDistances())
667            studentEl.addAttribute("shortDistances", "true");
668        if (student.isAllowDisabled())
669            studentEl.addAttribute("allowDisabled", "true");
670        if (student.hasMinCredit())
671            studentEl.addAttribute("minCredit", String.valueOf(student.getMinCredit()));
672        if (student.hasMaxCredit())
673            studentEl.addAttribute("maxCredit", String.valueOf(student.getMaxCredit()));
674        if (iSaveStudentInfo) {
675            for (AreaClassificationMajor acm : student.getAreaClassificationMajors()) {
676                Element acmEl = studentEl.addElement("acm");
677                if (acm.getArea() != null)
678                    acmEl.addAttribute("area", acm.getArea());
679                if (acm.getClassification() != null)
680                    acmEl.addAttribute("classification", acm.getClassification());
681                if (acm.getMajor() != null)
682                    acmEl.addAttribute("major", acm.getMajor());
683                if (acm.getConcentration() != null)
684                    acmEl.addAttribute("concentration", acm.getConcentration());
685                if (acm.getDegree() != null)
686                    acmEl.addAttribute("degree", acm.getDegree());
687                if (acm.getProgram() != null)
688                    acmEl.addAttribute("program", acm.getProgram());
689                if (acm.getAreaName() != null && iShowNames)
690                    acmEl.addAttribute("areaName", acm.getAreaName());
691                if (acm.getClassificationName() != null && iShowNames)
692                    acmEl.addAttribute("classificationName", acm.getClassificationName());
693                if (acm.getMajorName() != null && iShowNames)
694                    acmEl.addAttribute("majorName", acm.getMajorName());
695                if (acm.getConcentrationName() != null && iShowNames)
696                    acmEl.addAttribute("concentrationName", acm.getConcentrationName());
697                if (acm.getDegreeName() != null && iShowNames)
698                    acmEl.addAttribute("degreeName", acm.getDegreeName());
699                if (acm.getProgramName() != null && iShowNames)
700                    acmEl.addAttribute("programName", acm.getProgramName());
701                if (acm.getWeight() != 1.0)
702                    acmEl.addAttribute("weight", String.valueOf(acm.getWeight()));
703            }
704            for (AreaClassificationMajor acm : student.getAreaClassificationMinors()) {
705                Element acmEl = studentEl.addElement("acm");
706                if (acm.getArea() != null)
707                    acmEl.addAttribute("area", acm.getArea());
708                if (acm.getClassification() != null)
709                    acmEl.addAttribute("classification", acm.getClassification());
710                if (acm.getMajor() != null)
711                    acmEl.addAttribute("minor", acm.getMajor());
712                if (acm.getConcentration() != null)
713                    acmEl.addAttribute("concentration", acm.getConcentration());
714                if (acm.getDegree() != null)
715                    acmEl.addAttribute("degree", acm.getDegree());
716                if (acm.getProgram() != null)
717                    acmEl.addAttribute("program", acm.getProgram());
718                if (acm.getAreaName() != null && iShowNames)
719                    acmEl.addAttribute("areaName", acm.getAreaName());
720                if (acm.getClassificationName() != null && iShowNames)
721                    acmEl.addAttribute("classificationName", acm.getClassificationName());
722                if (acm.getMajorName() != null && iShowNames)
723                    acmEl.addAttribute("minorName", acm.getMajorName());
724                if (acm.getConcentrationName() != null && iShowNames)
725                    acmEl.addAttribute("concentrationName", acm.getConcentrationName());
726                if (acm.getDegreeName() != null && iShowNames)
727                    acmEl.addAttribute("degreeName", acm.getDegreeName());
728                if (acm.getProgramName() != null && iShowNames)
729                    acmEl.addAttribute("programName", acm.getProgramName());
730                if (acm.getWeight() != 1.0)
731                    acmEl.addAttribute("weight", String.valueOf(acm.getWeight()));
732            }
733            for (StudentGroup g : student.getGroups()) {
734                Element grEl = studentEl.addElement("group");
735                if (g.getType() != null && !g.getType().isEmpty())
736                    grEl.addAttribute("type", g.getType());
737                if (g.getReference() != null)
738                    grEl.addAttribute("reference", g.getReference());
739                if (g.getName() != null)
740                    grEl.addAttribute("name", g.getName());
741            }
742            for (String acc: student.getAccommodations())
743                studentEl.addElement("accommodation").addAttribute("reference", acc);
744        }
745        if (iShowNames && iSaveStudentInfo) {
746            for (Instructor adv: student.getAdvisors()) {
747                Element advEl = studentEl.addElement("advisor");
748                if (adv.getExternalId() != null)
749                    advEl.addAttribute("externalId", adv.getExternalId());
750                if (adv.getName() != null)
751                    advEl.addAttribute("name", adv.getName());
752                if (adv.getEmail() != null)
753                    advEl.addAttribute("email", adv.getEmail());
754            }
755        }
756        for (Unavailability unavailability: student.getUnavailabilities()) {
757            Element unavEl = studentEl.addElement("unavailability");
758            unavEl.addAttribute("offering", getId("offering", unavailability.getSection().getSubpart().getConfig().getOffering().getId()));
759            unavEl.addAttribute("section", getId("section", unavailability.getSection().getId()));
760            if (unavailability.isAllowOverlap()) unavEl.addAttribute("allowOverlap", "true");
761        }
762    }
763    
764    /**
765     * Save request
766     * @param studentEl student element to be populated
767     * @param request request to be saved
768     */
769    protected void saveRequest(Element studentEl, Request request) {
770        if (request instanceof FreeTimeRequest) {
771            saveFreeTimeRequest(studentEl.addElement("freeTime"), (FreeTimeRequest) request);
772        } else if (request instanceof CourseRequest) {
773            saveCourseRequest(studentEl.addElement("course"), (CourseRequest) request);
774        }
775    }
776    
777    /**
778     * Save free time request
779     * @param requestEl request element to be populated
780     * @param request free time request to be saved 
781     */
782    protected void saveFreeTimeRequest(Element requestEl, FreeTimeRequest request) {
783        requestEl.addAttribute("id", getId("request", request.getId()));
784        requestEl.addAttribute("priority", String.valueOf(request.getPriority()));
785        if (request.isAlternative())
786            requestEl.addAttribute("alternative", "true");
787        if (request.getWeight() != 1.0)
788            requestEl.addAttribute("weight", sStudentWeightFormat.format(request.getWeight()));
789        TimeLocation tl = request.getTime();
790        if (tl != null) {
791            requestEl.addAttribute("days", sDF[7].format(Long.parseLong(Integer.toBinaryString(tl
792                    .getDayCode()))));
793            requestEl.addAttribute("start", String.valueOf(tl.getStartSlot()));
794            requestEl.addAttribute("length", String.valueOf(tl.getLength()));
795            if (iShowNames && tl.getDatePatternId() != null)
796                requestEl.addAttribute("datePattern", tl.getDatePatternId().toString());
797            requestEl.addAttribute("dates", bitset2string(tl.getWeekCode()));
798            if (iShowNames)
799                requestEl.setText(tl.getLongName(true));
800        }
801        if (iSaveInitial && request.getInitialAssignment() != null) {
802            requestEl.addElement("initial");
803        }
804        if (iSaveCurrent && getAssignment().getValue(request) != null) {
805            requestEl.addElement("current");
806        }
807        if (iSaveBest && request.getBestAssignment() != null) {
808            requestEl.addElement("best");
809        }
810    }
811    
812    /**
813     * Save course request 
814     * @param requestEl request element to be populated
815     * @param request course request to be saved
816     */
817    protected void saveCourseRequest(Element requestEl, CourseRequest request) {
818        requestEl.addAttribute("id", getId("request", request.getId()));
819        requestEl.addAttribute("priority", String.valueOf(request.getPriority()));
820        if (request.isAlternative())
821            requestEl.addAttribute("alternative", "true");
822        if (request.getWeight() != 1.0)
823            requestEl.addAttribute("weight", sStudentWeightFormat.format(request.getWeight()));
824        requestEl.addAttribute("waitlist", request.isWaitlist() ? "true" : "false");
825        if (request.getRequestPriority() != RequestPriority.Normal)
826            requestEl.addAttribute("importance", request.getRequestPriority().name());
827        if (request.getRequestPriority() == RequestPriority.Critical)
828            requestEl.addAttribute("critical", "true");
829        if (request.getTimeStamp() != null)
830            requestEl.addAttribute("timeStamp", request.getTimeStamp().toString());
831        boolean first = true;
832        for (Course course : request.getCourses()) {
833            if (first)
834                requestEl.addAttribute("course", getId("course", course.getId()));
835            else
836                requestEl.addElement("alternative").addAttribute("course", getId("course", course.getId()));
837            first = false;
838        }
839        for (Choice choice : request.getWaitlistedChoices()) {
840            Element choiceEl = requestEl.addElement("waitlisted");
841            choiceEl.addAttribute("offering", getId("offering", choice.getOffering().getId()));
842            choiceEl.setText(choice.getId());
843        }
844        for (Choice choice : request.getSelectedChoices()) {
845            Element choiceEl = requestEl.addElement("selected");
846            choiceEl.addAttribute("offering", getId("offering", choice.getOffering().getId()));
847            choiceEl.setText(choice.getId());
848        }
849        for (Choice choice : request.getRequiredChoices()) {
850            Element choiceEl = requestEl.addElement("required");
851            choiceEl.addAttribute("offering", getId("offering", choice.getOffering().getId()));
852            choiceEl.setText(choice.getId());
853        }
854        if (iSaveInitial && request.getInitialAssignment() != null) {
855            saveEnrollment(requestEl.addElement("initial"), request.getInitialAssignment());
856        }
857        if (iSaveCurrent && getAssignment().getValue(request) != null) {
858            saveEnrollment(requestEl.addElement("current"), getAssignment().getValue(request));
859        }
860        if (iSaveBest && request.getBestAssignment() != null) {
861            saveEnrollment(requestEl.addElement("best"), request.getBestAssignment());
862        }
863        if (request.isFixed())
864            saveEnrollment(requestEl.addElement("fixed"), request.getFixedValue());
865        for (RequestGroup g: request.getRequestGroups()) {
866            Element groupEl = requestEl.addElement("group").addAttribute("id", getId("group", g.getId())).addAttribute("course", getId("course", g.getCourse().getId()));
867            if (iShowNames)
868                groupEl.addAttribute("name", g.getName());
869        }
870    }
871    
872    /**
873     * Save enrollment
874     * @param assignmentEl assignment element to be populated
875     * @param enrollment enrollment to be saved
876     */
877    protected void saveEnrollment(Element assignmentEl, Enrollment enrollment) {
878        if (enrollment.getReservation() != null)
879            assignmentEl.addAttribute("reservation", getId("reservation", enrollment.getReservation().getId()));
880        if (enrollment.getCourse() != null)
881            assignmentEl.addAttribute("course", getId("course", enrollment.getCourse().getId()));
882        for (Section section : enrollment.getSections()) {
883            Element sectionEl = assignmentEl.addElement("section").addAttribute("id",
884                    getId("section", section.getId()));
885            if (iShowNames)
886                sectionEl.setText(section.getName() + " " +
887                        (section.getTime() == null ? " Arr Hrs" : " " + section.getTime().getLongName(true)) +
888                        (section.getNrRooms() == 0 ? "" : " " + section.getPlacement().getRoomName(",")) +
889                        (section.hasInstructors() ? " " + section.getInstructorNames(",") : ""));
890        }
891    }
892    
893    /**
894     * Save linked sections
895     * @param root document root
896     */
897    protected void saveLinkedSections(Element root) {
898        Element constrainstEl = root.addElement("constraints");
899        for (LinkedSections linkedSections: getModel().getLinkedSections()) {
900            Element linkEl = constrainstEl.addElement("linked-sections");
901            linkEl.addAttribute("mustBeUsed", linkedSections.isMustBeUsed() ? "true" : "false");
902            for (Offering offering: linkedSections.getOfferings())
903                for (Subpart subpart: linkedSections.getSubparts(offering))
904                    for (Section section: linkedSections.getSections(subpart))
905                        linkEl.addElement("section")
906                            .addAttribute("offering", getId("offering", offering.getId()))
907                            .addAttribute("id", getId("section", section.getId()));
908        }
909    }
910    
911    /**
912     * Save travel times
913     * @param root document root
914     */
915    protected void saveTravelTimes(Element root) {
916        if (getModel().getDistanceMetric() != null) {
917            Map<Long, Map<Long, Integer>> travelTimes = getModel().getDistanceMetric().getTravelTimes();
918            if (travelTimes != null) {
919                Element travelTimesEl = root.addElement("travel-times");
920                for (Map.Entry<Long, Map<Long, Integer>> e1: travelTimes.entrySet())
921                    for (Map.Entry<Long, Integer> e2: e1.getValue().entrySet())
922                        travelTimesEl.addElement("travel-time")
923                            .addAttribute("id1", getId("room", e1.getKey().toString()))
924                            .addAttribute("id2", getId("room", e2.getKey().toString()))
925                            .addAttribute("minutes", e2.getValue().toString());
926            }
927        }
928    }
929}