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