    001package net.sf.cpsolver.coursett;
    002
    003import java.io.File;
    004import java.io.FileOutputStream;
    005import java.io.IOException;
    006import java.text.DecimalFormat;
    007import java.text.DecimalFormatSymbols;
    008import java.util.ArrayList;
    009import java.util.BitSet;
    010import java.util.Collections;
    011import java.util.Date;
    012import java.util.HashSet;
    013import java.util.HashMap;
    014import java.util.Iterator;
    015import java.util.List;
    016import java.util.Locale;
    017import java.util.Map;
    018import java.util.Set;
    019import java.util.TreeSet;
    020
    021import net.sf.cpsolver.coursett.constraint.ClassLimitConstraint;
    022import net.sf.cpsolver.coursett.constraint.DiscouragedRoomConstraint;
    023import net.sf.cpsolver.coursett.constraint.FlexibleConstraint;
    024import net.sf.cpsolver.coursett.constraint.GroupConstraint;
    025import net.sf.cpsolver.coursett.constraint.IgnoreStudentConflictsConstraint;
    026import net.sf.cpsolver.coursett.constraint.InstructorConstraint;
    027import net.sf.cpsolver.coursett.constraint.MinimizeNumberOfUsedGroupsOfTime;
    028import net.sf.cpsolver.coursett.constraint.MinimizeNumberOfUsedRoomsConstraint;
    029import net.sf.cpsolver.coursett.constraint.RoomConstraint;
    030import net.sf.cpsolver.coursett.constraint.SpreadConstraint;
    031import net.sf.cpsolver.coursett.model.Configuration;
    032import net.sf.cpsolver.coursett.model.Lecture;
    033import net.sf.cpsolver.coursett.model.Placement;
    034import net.sf.cpsolver.coursett.model.RoomLocation;
    035import net.sf.cpsolver.coursett.model.RoomSharingModel;
    036import net.sf.cpsolver.coursett.model.Student;
    037import net.sf.cpsolver.coursett.model.TimeLocation;
    038import net.sf.cpsolver.ifs.model.Constraint;
    039import net.sf.cpsolver.ifs.solver.Solver;
    040import net.sf.cpsolver.ifs.util.Progress;
    041import net.sf.cpsolver.ifs.util.ToolBox;
    042
    043import org.dom4j.Document;
    044import org.dom4j.DocumentHelper;
    045import org.dom4j.Element;
    046import org.dom4j.io.OutputFormat;
    047import 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 Tomáš Müller<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
    116public 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 (lecture.getWeight() != 1.0)
    287                classEl.addAttribute("weight", String.valueOf(lecture.getWeight()));
    288            if (iShowNames && lecture.getSolverGroupId() != null)
    289                classEl.addAttribute("solverGroup", getId("solverGroup", lecture.getSolverGroupId()));
    290            if (lecture.getParent() == null && lecture.getConfiguration() != null) {
    291                if (!iShowNames)
    292                    classEl.addAttribute("offering", getId("offering", lecture.getConfiguration().getOfferingId()
    293                            .toString()));
    294                classEl.addAttribute("config", getId("config", lecture.getConfiguration().getConfigId().toString()));
    295                if (iShowNames && configs.add(lecture.getConfiguration())) {
    296                    configsEl.addElement("config").addAttribute("id",
    297                            getId("config", lecture.getConfiguration().getConfigId().toString())).addAttribute("limit",
    298                            String.valueOf(lecture.getConfiguration().getLimit())).addAttribute("offering",
    299                            getId("offering", lecture.getConfiguration().getOfferingId().toString()));
    300                }
    301            }
    302            classEl.addAttribute("committed", (lecture.isCommitted() ? "true" : "false"));
    303            if (lecture.getParent() != null)
    304                classEl.addAttribute("parent", getId("class", lecture.getParent().getClassId()));
    305            if (lecture.getSchedulingSubpartId() != null)
    306                classEl.addAttribute("subpart", getId("subpart", lecture.getSchedulingSubpartId()));
    307            if (iShowNames && lecture.isCommitted() && placement != null && placement.getAssignmentId() != null) {
    308                classEl.addAttribute("assignment", getId("assignment", placement.getAssignmentId()));
    309            }
    310            if (!lecture.isCommitted()) {
    311                if (lecture.minClassLimit() == lecture.maxClassLimit()) {
    312                    classEl.addAttribute("classLimit", String.valueOf(lecture.maxClassLimit()));
    313                } else {
    314                    classEl.addAttribute("minClassLimit", String.valueOf(lecture.minClassLimit()));
    315                    classEl.addAttribute("maxClassLimit", String.valueOf(lecture.maxClassLimit()));
    316                }
    317                if (lecture.roomToLimitRatio() != 1.0f)
    318                    classEl.addAttribute("roomToLimitRatio", sStudentWeightFormat.format(lecture.roomToLimitRatio()));
    319            }
    320            if (lecture.getNrRooms() != 1)
    321                classEl.addAttribute("nrRooms", String.valueOf(lecture.getNrRooms()));
    322            if (iShowNames)
    323                classEl.addAttribute("name", lecture.getName());
    324            if (lecture.getDeptSpreadConstraint() != null) {
    325                classEl.addAttribute("department", getId("dept", lecture.getDeptSpreadConstraint().getDepartmentId()));
    326                depts.put(lecture.getDeptSpreadConstraint().getDepartmentId(), lecture.getDeptSpreadConstraint()
    327                        .getName());
    328            }
    329            if (lecture.getScheduler() != null)
    330                classEl.addAttribute("scheduler", getId("dept", lecture.getScheduler()));
    331            for (InstructorConstraint ic : lecture.getInstructorConstraints()) {
    332                Element instrEl = classEl.addElement("instructor")
    333                        .addAttribute("id", getId("inst", ic.getResourceId()));
    334                if ((lecture.isCommitted() || iSaveCurrent) && placement != null)
    335                    instrEl.addAttribute("solution", "true");
    336                if (iSaveInitial && initialPlacement != null)
    337                    instrEl.addAttribute("initial", "true");
    338                if (iSaveBest && bestPlacement != null && !bestPlacement.equals(placement))
    339                    instrEl.addAttribute("best", "true");
    340            }
    341            for (RoomLocation rl : lecture.roomLocations()) {
    342                Element roomLocationEl = classEl.addElement("room");
    343                roomLocationEl.addAttribute("id", getId("room", rl.getId()));
    344                roomLocationEl.addAttribute("pref", String.valueOf(rl.getPreference()));
    345                if ((lecture.isCommitted() || iSaveCurrent) && placement != null
    346                        && placement.hasRoomLocation(rl.getId()))
    347                    roomLocationEl.addAttribute("solution", "true");
    348                if (iSaveInitial && initialPlacement != null && initialPlacement.hasRoomLocation(rl.getId()))
    349                    roomLocationEl.addAttribute("initial", "true");
    350                if (iSaveBest && bestPlacement != null && !bestPlacement.equals(placement)
    351                        && bestPlacement.hasRoomLocation(rl.getId()))
    352                    roomLocationEl.addAttribute("best", "true");
    353                if (!roomElements.containsKey(getId("room", rl.getId()))) {
    354                    // room location without room constraint
    355                    Element roomEl = roomsEl.addElement("room").addAttribute("id", getId("room", rl.getId()));
    356                    roomEl.addAttribute("constraint", "false");
    357                    if (!iConvertIds && rl.getBuildingId() != null)
    358                        roomEl.addAttribute("building", getId("bldg", rl.getBuildingId()));
    359                    if (iShowNames) {
    360                        roomEl.addAttribute("name", rl.getName());
    361                    }
    362                    roomElements.put(getId("room", rl.getId()), roomEl);
    363                    roomEl.addAttribute("capacity", String.valueOf(rl.getRoomSize()));
    364                    if (rl.getPosX() != null && rl.getPosY() != null)
    365                        roomEl.addAttribute("location", rl.getPosX() + "," + rl.getPosY());
    366                    if (rl.getIgnoreTooFar())
    367                        roomEl.addAttribute("ignoreTooFar", "true");
    368                }
    369            }
    370            boolean first = true;
    371            Set<Long> dp = new HashSet<Long>();
    372            for (TimeLocation tl : lecture.timeLocations()) {
    373                Element timeLocationEl = classEl.addElement("time");
    374                timeLocationEl.addAttribute("days", sDF[7].format(Long.parseLong(Integer
    375                        .toBinaryString(tl.getDayCode()))));
    376                timeLocationEl.addAttribute("start", String.valueOf(tl.getStartSlot()));
    377                timeLocationEl.addAttribute("length", String.valueOf(tl.getLength()));
    378                timeLocationEl.addAttribute("breakTime", String.valueOf(tl.getBreakTime()));
    379                if (iShowNames) {
    380                    timeLocationEl.addAttribute("pref", String.valueOf(tl.getPreference()));
    381                    timeLocationEl.addAttribute("npref", String.valueOf(tl.getNormalizedPreference()));
    382                } else {
    383                    timeLocationEl.addAttribute("pref", String.valueOf(tl.getNormalizedPreference()));
    384                }
    385                if (!iConvertIds && tl.getTimePatternId() != null)
    386                    timeLocationEl.addAttribute("pattern", getId("pat", tl.getTimePatternId()));
    387                if (tl.getDatePatternId() != null && dp.add(tl.getDatePatternId())) {
    388                    Element dateEl = classEl.addElement("date");
    389                    dateEl.addAttribute("id", getId("dpat", String.valueOf(tl.getDatePatternId())));
    390                    if (iShowNames)
    391                        dateEl.addAttribute("name", tl.getDatePatternName());
    392                    dateEl.addAttribute("pattern", bitset2string(tl.getWeekCode()));
    393                }
    394                if (tl.getDatePatternPreference() != 0)
    395                    timeLocationEl.addAttribute("datePref", String.valueOf(tl.getDatePatternPreference()));
    396                if (tl.getTimePatternId() == null && first) {
    397                    if (iShowNames)
    398                        classEl.addAttribute("datePatternName", tl.getDatePatternName());
    399                    classEl.addAttribute("dates", bitset2string(tl.getWeekCode()));
    400                    first = false;
    401                }
    402                if (tl.getDatePatternId() != null) {
    403                    timeLocationEl.addAttribute("date", getId("dpat", String.valueOf(tl.getDatePatternId())));
    404                }
    405                if ((lecture.isCommitted() || iSaveCurrent) && placement != null
    406                        && placement.getTimeLocation().equals(tl))
    407                    timeLocationEl.addAttribute("solution", "true");
    408                if (iSaveInitial && initialPlacement != null && initialPlacement.getTimeLocation().equals(tl))
    409                    timeLocationEl.addAttribute("initial", "true");
    410                if (iSaveBest && bestPlacement != null && !bestPlacement.equals(placement)
    411                        && bestPlacement.getTimeLocation().equals(tl))
    412                    timeLocationEl.addAttribute("best", "true");
    413            }
    414        }
    415
    416        for (InstructorConstraint ic : getModel().getInstructorConstraints()) {
    417            if (iShowNames || ic.isIgnoreDistances()) {
    418                Element instrEl = instructorsEl.addElement("instructor").addAttribute("id",
    419                        getId("inst", ic.getResourceId()));
    420                if (iShowNames) {
    421                    if (ic.getPuid() != null && ic.getPuid().length() > 0)
    422                        instrEl.addAttribute("puid", ic.getPuid());
    423                    instrEl.addAttribute("name", ic.getName());
    424                    if (ic.getType() != null && iShowNames)
    425                        instrEl.addAttribute("type", ic.getType().toString());
    426                }
    427                if (ic.isIgnoreDistances()) {
    428                    instrEl.addAttribute("ignDist", "true");
    429                }
    430            }
    431            if (ic.getUnavailabilities() != null) {
    432                for (Placement placement: ic.getUnavailabilities()) {
    433                    Lecture lecture = placement.variable();
    434                    Element classEl = classElements.get(lecture.getClassId());
    435                    classEl.addElement("instructor").addAttribute("id", getId("inst", ic.getResourceId())).addAttribute("solution", "true");
    436                }
    437            }
    438        }
    439        if (instructorsEl.elements().isEmpty())
    440            root.remove(instructorsEl);
    441
    442        Element grConstraintsEl = root.addElement("groupConstraints");
    443        for (GroupConstraint gc : getModel().getGroupConstraints()) {
    444            Element grEl = grConstraintsEl.addElement("constraint").addAttribute("id",
    445                    getId("gr", String.valueOf(gc.getId())));
    446            grEl.addAttribute("type", gc.getType().reference());
    447            grEl.addAttribute("pref", gc.getPrologPreference());
    448            for (Lecture l : gc.variables()) {
    449                grEl.addElement("class").addAttribute("id", getId("class", l.getClassId()));
    450            }
    451        }
    452        for (SpreadConstraint spread : getModel().getSpreadConstraints()) {
    453            Element grEl = grConstraintsEl.addElement("constraint").addAttribute("id",
    454                    getId("gr", String.valueOf(spread.getId())));
    455            grEl.addAttribute("type", "SPREAD");
    456            grEl.addAttribute("pref", Constants.sPreferenceRequired);
    457            if (iShowNames)
    458                grEl.addAttribute("name", spread.getName());
    459            for (Lecture l : spread.variables()) {
    460                grEl.addElement("class").addAttribute("id", getId("class", l.getClassId()));
    461            }
    462        }
    463        for (Constraint<Lecture, Placement> c : getModel().constraints()) {
    464            if (c instanceof MinimizeNumberOfUsedRoomsConstraint) {
    465                Element grEl = grConstraintsEl.addElement("constraint").addAttribute("id",
    466                        getId("gr", String.valueOf(c.getId())));
    467                grEl.addAttribute("type", "MIN_ROOM_USE");
    468                grEl.addAttribute("pref", Constants.sPreferenceRequired);
    469                for (Lecture l : c.variables()) {
    470                    grEl.addElement("class").addAttribute("id", getId("class", l.getClassId()));
    471                }
    472            }
    473            if (c instanceof MinimizeNumberOfUsedGroupsOfTime) {
    474                Element grEl = grConstraintsEl.addElement("constraint").addAttribute("id",
    475                        getId("gr", String.valueOf(c.getId())));
    476                grEl.addAttribute("type", ((MinimizeNumberOfUsedGroupsOfTime) c).getConstraintName());
    477                grEl.addAttribute("pref", Constants.sPreferenceRequired);
    478                for (Lecture l : c.variables()) {
    479                    grEl.addElement("class").addAttribute("id", getId("class", l.getClassId()));
    480                }
    481            }
    482            if (c instanceof IgnoreStudentConflictsConstraint) {
    483                Element grEl = grConstraintsEl.addElement("constraint").addAttribute("id", getId("gr", String.valueOf(c.getId())));
    484                grEl.addAttribute("type", IgnoreStudentConflictsConstraint.REFERENCE);
    485                grEl.addAttribute("pref", Constants.sPreferenceRequired);
    486                for (Lecture l : c.variables()) {
    487                    grEl.addElement("class").addAttribute("id", getId("class", l.getClassId()));
    488                }
    489            }
    490        }
    491        for (ClassLimitConstraint clc : getModel().getClassLimitConstraints()) {
    492            Element grEl = grConstraintsEl.addElement("constraint").addAttribute("id",
    493                    getId("gr", String.valueOf(clc.getId())));
    494            grEl.addAttribute("type", "CLASS_LIMIT");
    495            grEl.addAttribute("pref", Constants.sPreferenceRequired);
    496            if (clc.getParentLecture() != null) {
    497                grEl.addElement("parentClass").addAttribute("id", getId("class", clc.getParentLecture().getClassId()));
    498            } else
    499                grEl.addAttribute("courseLimit", String.valueOf(clc.classLimit() - clc.getClassLimitDelta()));
    500            if (clc.getClassLimitDelta() != 0)
    501                grEl.addAttribute("delta", String.valueOf(clc.getClassLimitDelta()));
    502            if (iShowNames)
    503                grEl.addAttribute("name", clc.getName());
    504            for (Lecture l : clc.variables()) {
    505                grEl.addElement("class").addAttribute("id", getId("class", l.getClassId()));
    506            }
    507        }
    508        for (FlexibleConstraint gc : getModel().getFlexibleConstraints()) {
    509            Element flEl = grConstraintsEl.addElement("constraint").addAttribute("id",
    510                    getId("gr", String.valueOf(gc.getId())));
    511            flEl.addAttribute("reference", gc.getReference());
    512            flEl.addAttribute("owner", gc.getOwner());
    513            flEl.addAttribute("pref", gc.getPrologPreference());
    514            flEl.addAttribute("type", gc.getType().toString());
    515            for (Lecture l : gc.variables()) {
    516                flEl.addElement("class").addAttribute("id", getId("class", l.getClassId()));
    517            }
    518        }
    519
    520        HashMap<Student, List<String>> students = new HashMap<Student, List<String>>();
    521        for (Lecture lecture : vars) {
    522            for (Student student : lecture.students()) {
    523                List<String> enrls = students.get(student);
    524                if (enrls == null) {
    525                    enrls = new ArrayList<String>();
    526                    students.put(student, enrls);
    527                }
    528                enrls.add(getId("class", lecture.getClassId()));
    529            }
    530        }
    531
    532        Element studentsEl = root.addElement("students");
    533        for (Student student: new TreeSet<Student>(students.keySet())) {
    534            Element stEl = studentsEl.addElement("student").addAttribute("id", getId("student", student.getId()));
    535            if (iShowNames) {
    536                if (student.getAcademicArea() != null)
    537                    stEl.addAttribute("area", student.getAcademicArea());
    538                if (student.getAcademicClassification() != null)
    539                    stEl.addAttribute("classification", student.getAcademicClassification());
    540                if (student.getMajor() != null)
    541                    stEl.addAttribute("major", student.getMajor());
    542                if (student.getCurriculum() != null)
    543                    stEl.addAttribute("curriculum", student.getCurriculum());
    544            }
    545            for (Map.Entry<Long, Double> entry : student.getOfferingsMap().entrySet()) {
    546                Long offeringId = entry.getKey();
    547                Double weight = entry.getValue();
    548                Element offEl = stEl.addElement("offering")
    549                        .addAttribute("id", getId("offering", offeringId.toString()));
    550                if (weight.doubleValue() != 1.0)
    551                    offEl.addAttribute("weight", sStudentWeightFormat.format(weight));
    552                Double priority = student.getPriority(offeringId);
    553                if (priority != null)
    554                    offEl.addAttribute("priority", priority.toString());
    555            }
    556            if (iExportStudentSectioning || getModel().unassignedVariables().isEmpty()
    557                    || student.getOfferingsMap().isEmpty()) {
    558                List<String> lectures = students.get(student);
    559                Collections.sort(lectures);
    560                for (String classId : lectures) {
    561                    stEl.addElement("class").addAttribute("id", classId);
    562                }
    563            }
    564            Map<Long, Set<Lecture>> canNotEnroll = student.canNotEnrollSections();
    565            if (canNotEnroll != null) {
    566                for (Set<Lecture> canNotEnrollLects: canNotEnroll.values()) {
    567                    for (Iterator<Lecture> i3 = canNotEnrollLects.iterator(); i3.hasNext();) {
    568                        stEl.addElement("prohibited-class")
    569                                .addAttribute("id", getId("class", (i3.next()).getClassId()));
    570                    }
    571                }
    572            }
    573
    574            if (student.getCommitedPlacements() != null) {
    575                for (Placement placement : student.getCommitedPlacements()) {
    576                    stEl.addElement("class").addAttribute("id", getId("class", placement.variable().getClassId()));
    577                }
    578            }
    579
    580            if (student.getInstructor() != null)
    581                stEl.addAttribute("instructor", getId("inst", student.getInstructor().getResourceId()));
    582        }
    583
    584        if (getModel().getProperties().getPropertyInt("MPP.GenTimePert", 0) > 0) {
    585            Element perturbationsEl = root.addElement("perturbations");
    586            int nrChanges = getModel().getProperties().getPropertyInt("MPP.GenTimePert", 0);
    587            List<Lecture> lectures = new ArrayList<Lecture>();
    588            while (lectures.size() < nrChanges) {
    589                Lecture lecture = ToolBox.random(getModel().assignedVariables());
    590                if (lecture.isCommitted() || lecture.timeLocations().size() <= 1 || lectures.contains(lecture))
    591                    continue;
    592                Placement placement = lecture.getAssignment();
    593                TimeLocation tl = placement.getTimeLocation();
    594                perturbationsEl.addElement("class").addAttribute("id", getId("class", lecture.getClassId()))
    595                        .addAttribute("days", sDF[7].format(Long.parseLong(Integer.toBinaryString(tl.getDayCode()))))
    596                        .addAttribute("start", String.valueOf(tl.getStartSlot())).addAttribute("length",
    597                                String.valueOf(tl.getLength()));
    598                lectures.add(lecture);
    599            }
    600        }
    601
    602        for (Map.Entry<Long, String> entry : depts.entrySet()) {
    603            Long id = entry.getKey();
    604            String name = entry.getValue();
    605            if (iShowNames) {
    606                departmentsEl.addElement("department").addAttribute("id", getId("dept", id.toString())).addAttribute(
    607                        "name", name);
    608            }
    609        }
    610        if (departmentsEl.elements().isEmpty())
    611            root.remove(departmentsEl);
    612
    613        if (iShowNames) {
    614            Progress.getInstance(getModel()).save(root);
    615
    616            try {
    617                getSolver().getClass().getMethod("save", new Class[] { Element.class }).invoke(getSolver(),
    618                        new Object[] { root });
    619            } catch (Exception e) {
    620            }
    621        }
    622
    623        FileOutputStream fos = null;
    624        try {
    625            fos = new FileOutputStream(outFile);
    626            (new XMLWriter(fos, OutputFormat.createPrettyPrint())).write(document);
    627            fos.flush();
    628            fos.close();
    629            fos = null;
    630        } finally {
    631            try {
    632                if (fos != null)
    633                    fos.close();
    634            } catch (IOException e) {
    635            }
    636        }
    637
    638        if (iConvertIds)
    639            iIdConvertor.save();
    640    }
    641}