org.unitime.timetable.solver.TimetableSolver.java Source code

Java tutorial

Introduction

Here is the source code for org.unitime.timetable.solver.TimetableSolver.java

Source

/*
 * Licensed to The Apereo Foundation under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for
 * additional information regarding copyright ownership.
 *
 * The Apereo Foundation licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at:
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
*/
package org.unitime.timetable.solver;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;
import java.util.Vector;
import java.util.concurrent.locks.Lock;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cpsolver.coursett.TimetableXMLLoader;
import org.cpsolver.coursett.TimetableXMLSaver;
import org.cpsolver.coursett.constraint.ClassLimitConstraint;
import org.cpsolver.coursett.constraint.DepartmentSpreadConstraint;
import org.cpsolver.coursett.constraint.GroupConstraint;
import org.cpsolver.coursett.constraint.InstructorConstraint;
import org.cpsolver.coursett.constraint.RoomConstraint;
import org.cpsolver.coursett.constraint.SpreadConstraint;
import org.cpsolver.coursett.model.Lecture;
import org.cpsolver.coursett.model.Placement;
import org.cpsolver.coursett.model.RoomLocation;
import org.cpsolver.coursett.model.Student;
import org.cpsolver.coursett.model.TimeLocation;
import org.cpsolver.coursett.model.TimetableModel;
import org.cpsolver.ifs.extension.ConflictStatistics;
import org.cpsolver.ifs.extension.Extension;
import org.cpsolver.ifs.model.Constraint;
import org.cpsolver.ifs.solver.ParallelSolver;
import org.cpsolver.ifs.solver.Solver;
import org.cpsolver.ifs.util.CSVFile;
import org.cpsolver.ifs.util.Callback;
import org.cpsolver.ifs.util.DataProperties;
import org.cpsolver.ifs.util.Progress;
import org.cpsolver.ifs.util.ProgressWriter;
import org.cpsolver.ifs.util.ToolBox;
import org.cpsolver.ifs.util.CSVFile.CSVField;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.type.LongType;
import org.unitime.timetable.defaults.ApplicationProperty;
import org.unitime.timetable.gwt.server.Query;
import org.unitime.timetable.gwt.server.Query.TermMatcher;
import org.unitime.timetable.interfaces.RoomAvailabilityInterface.TimeBlock;
import org.unitime.timetable.model.Assignment;
import org.unitime.timetable.model.Class_;
import org.unitime.timetable.model.Department;
import org.unitime.timetable.model.DepartmentalInstructor;
import org.unitime.timetable.model.Location;
import org.unitime.timetable.model.RoomType;
import org.unitime.timetable.model.Solution;
import org.unitime.timetable.model.SolverGroup;
import org.unitime.timetable.model.SubjectArea;
import org.unitime.timetable.model.TimePattern;
import org.unitime.timetable.model.dao.DatePatternDAO;
import org.unitime.timetable.model.dao.DepartmentDAO;
import org.unitime.timetable.model.dao.DepartmentalInstructorDAO;
import org.unitime.timetable.model.dao.LocationDAO;
import org.unitime.timetable.model.dao.RoomTypeDAO;
import org.unitime.timetable.model.dao.SolutionDAO;
import org.unitime.timetable.model.dao.SolverGroupDAO;
import org.unitime.timetable.model.dao.TimePatternDAO;
import org.unitime.timetable.solver.interactive.ClassAssignmentDetails;
import org.unitime.timetable.solver.interactive.Hint;
import org.unitime.timetable.solver.interactive.Suggestion;
import org.unitime.timetable.solver.interactive.Suggestions;
import org.unitime.timetable.solver.interactive.SuggestionsModel;
import org.unitime.timetable.solver.remote.BackupFileFilter;
import org.unitime.timetable.solver.ui.AssignmentPreferenceInfo;
import org.unitime.timetable.solver.ui.ConflictStatisticsInfo;
import org.unitime.timetable.solver.ui.DeptBalancingReport;
import org.unitime.timetable.solver.ui.DiscouragedInstructorBtbReport;
import org.unitime.timetable.solver.ui.PerturbationReport;
import org.unitime.timetable.solver.ui.PropertiesInfo;
import org.unitime.timetable.solver.ui.RoomReport;
import org.unitime.timetable.solver.ui.SameSubpartBalancingReport;
import org.unitime.timetable.solver.ui.SolverUnassignedClassesModel;
import org.unitime.timetable.solver.ui.StudentConflictsReport;
import org.unitime.timetable.solver.ui.ViolatedDistrPreferencesReport;
import org.unitime.timetable.util.Constants;
import org.unitime.timetable.webutil.timegrid.SolverGridModel;
import org.unitime.timetable.webutil.timegrid.TimetableGridContext;
import org.unitime.timetable.webutil.timegrid.TimetableGridModel;

/**
 * @author Tomas Muller
 */
public class TimetableSolver extends ParallelSolver<Lecture, Placement> implements SolverProxy {
    private static Log sLog = LogFactory.getLog(TimetableSolver.class);
    private boolean iWorking = false;
    private Date iLoadedDate = null;
    private int iDebugLevel = Progress.MSGLEVEL_INFO;
    private Vector iAssignmentRecords = new Vector();
    private Vector iBestAssignmentRecords = new Vector();
    private ConflictStatisticsInfo iCbsInfo = null;
    private CommitedClassAssignmentProxy iCommitedClassAssignmentProxy = null;

    private long iLastTimeStamp = System.currentTimeMillis();
    private boolean iIsPassivated = false;
    private Map iProgressBeforePassivation = null;
    private PropertiesInfo iGlobalInfoBeforePassivation = null;
    private Map<String, String> iCurrentSolutionInfoBeforePassivation = null;
    private Map<String, String> iBestSolutionInfoBeforePassivation = null;
    private File iPassivationFolder = null;
    private String iPassivationPuid = null;
    private Thread iWorkThread = null;

    private SolverDisposeListener iSolverDisposeListener;

    public TimetableSolver(DataProperties properties, SolverDisposeListener solverDisposeListener) {
        super(properties);
        iCommitedClassAssignmentProxy = new CommitedClassAssignmentProxy();
        iSolverDisposeListener = solverDisposeListener;
    }

    public Date getLoadedDate() {
        if (iLoadedDate == null && !isPassivated()) {
            List<Progress.Message> log = Progress.getInstance(currentSolution().getModel()).getLog();
            if (log != null && !log.isEmpty()) {
                iLoadedDate = log.get(0).getDate();
            }
        }
        return iLoadedDate;
    }

    public org.cpsolver.ifs.solution.Solution<Lecture, Placement> currentSolution() {
        activateIfNeeded();
        return super.currentSolution();
    }

    public void start() {
        activateIfNeeded();
        iCbsInfo = null;
        super.start();
    }

    public String getLog() {
        return Progress.getInstance(currentSolution().getModel()).getHtmlLog(iDebugLevel, true);
    }

    public String getLog(int level, boolean includeDate) {
        return Progress.getInstance(currentSolution().getModel()).getHtmlLog(level, includeDate);
    }

    public String getLog(int level, boolean includeDate, String fromStage) {
        return Progress.getInstance(currentSolution().getModel()).getHtmlLog(level, includeDate, fromStage);
    }

    public String getNote() {
        return getProperties().getProperty("General.Note");
    }

    public void setNote(String note) {
        getProperties().setProperty("General.Note", note);
    }

    public void setDebugLevel(int level) {
        iDebugLevel = level;
    }

    public int getDebugLevel() {
        return iDebugLevel;
    }

    public boolean isWorking() {
        if (isRunning())
            return true;
        return iWorking;
    }

    public void restoreBest() {
        iAssignmentRecords = new Vector(iBestAssignmentRecords);
        currentSolution().restoreBest();
    }

    public void saveBest() {
        iBestAssignmentRecords = new Vector(iAssignmentRecords);
        currentSolution().saveBest();
    }

    public Map getProgress() {
        if (isPassivated())
            return iProgressBeforePassivation;
        try {
            Hashtable ret = new Hashtable();
            Progress p = Progress.getInstance(super.currentSolution().getModel());
            ret.put("STATUS", p.getStatus());
            ret.put("PHASE", p.getPhase());
            ret.put("PROGRESS", new Long(p.getProgress()));
            ret.put("MAX_PROGRESS", new Long(p.getProgressMax()));
            ret.put("VERSION", Constants.getVersion());
            return ret;
        } catch (Exception e) {
            sLog.error(e.getMessage(), e);
            return null;
        }
    }

    public void finalSectioning() {
        iWorkThread = new FinalSectioning();
        iWorkThread.start();
    }

    public class FinalSectioning extends Thread {
        public void run() {
            setName("FinalSectioning");
            iWorking = true;
            try {
                ((TimetableModel) currentSolution().getModel()).switchStudents(currentSolution().getAssignment());
                //new EnrollmentCheck((TimetableModel)currentSolution().getModel()).checkStudentEnrollments(Progress.getInstance(currentSolution().getModel()));
            } finally {
                iWorking = false;
                Progress.getInstance(currentSolution().getModel()).setStatus("Awaiting commands ...");
            }
            afterFinalSectioning();
        }
    }

    public void dispose() {
        disposeNoInherit(true);
    }

    private void disposeNoInherit(boolean unregister) {
        super.dispose();
        iAssignmentRecords.clear();
        iBestAssignmentRecords.clear();
        iCbsInfo = null;
        if (currentSolution() != null && currentSolution().getModel() != null)
            Progress.removeInstance(currentSolution().getModel());
        setInitalSolution((org.cpsolver.ifs.solution.Solution) null);
        if (unregister && iSolverDisposeListener != null)
            iSolverDisposeListener.onDispose();
    }

    protected void onFinish() {
        super.onFinish();
        try {
            iWorking = true;
            if (currentSolution().getBestInfo() != null)
                currentSolution().restoreBest();
            if (getProperties().getPropertyBoolean("General.SwitchStudents", true)) {
                ((TimetableModel) currentSolution().getModel()).switchStudents(currentSolution().getAssignment());
                currentSolution().saveBest();
            }
            if (currentSolution().getBestInfo() != null
                    && getProperties().getPropertyBoolean("General.Save", false)) {
                TimetableDatabaseSaver saver = new TimetableDatabaseSaver(this);
                Lock lock = currentSolution().getLock().readLock();
                lock.lock();
                try {
                    saver.save();
                } finally {
                    lock.unlock();
                }
            }
            int repeat = getProperties().getPropertyInt("Test.Repeat", 0);
            if (repeat > 0) {
                getProperties().setProperty("Test.Repeat", String.valueOf(repeat - 1));
                getProperties().remove("General.SolutionId");
                load(getProperties());
                return;
            }
            if (getProperties().getPropertyBoolean("General.Unload", false)) {
                dispose();
            } else {
                Progress.getInstance(currentSolution().getModel()).setStatus("Awaiting commands ...");
            }
        } finally {
            iWorking = false;
        }
    }

    protected void onStop() {
        super.onStop();
        if (currentSolution().getBestInfo() != null)
            currentSolution().restoreBest();
    }

    public void save(boolean createNewSolution, boolean commitSolution) {
        iWorking = true;
        getProperties().setProperty("General.CreateNewSolution", (createNewSolution ? "true" : "false"));
        if (createNewSolution)
            getProperties().remove("General.SolutionId");
        getProperties().setProperty("General.CommitSolution", (commitSolution ? "true" : "false"));
        TimetableDatabaseSaver saver = new TimetableDatabaseSaver(this);
        saver.setCallback(getSavingDoneCallback());
        iWorkThread = new Thread(saver);
        iWorkThread.setPriority(THREAD_PRIORITY);
        iWorkThread.start();
    }

    public void load(DataProperties properties) {
        iAssignmentRecords.clear();
        iBestAssignmentRecords.clear();
        iCbsInfo = null;
        sLog.debug("History cleared");
        setProperties(properties);
        TimetableModel model = new TimetableModel(getProperties());
        Progress.getInstance(model).addProgressListener(new ProgressWriter(System.out));

        iWorking = true;
        setInitalSolution(model);
        initSolver();

        TimetableDatabaseLoader loader = new TimetableDatabaseLoader(model, currentSolution().getAssignment());
        loader.setCallback(getLoadingDoneCallback());
        iWorkThread = new Thread(loader);
        iWorkThread.setPriority(THREAD_PRIORITY);
        iWorkThread.start();
    }

    public void reload(DataProperties properties) {
        if (currentSolution() == null || currentSolution().getModel() == null) {
            load(properties);
            return;
        }

        Callback callBack = getReloadingDoneCallback();
        setProperties(properties);
        TimetableModel model = new TimetableModel(getProperties());

        iWorking = true;
        Progress.changeInstance(currentSolution().getModel(), model);
        setInitalSolution(model);
        initSolver();

        TimetableDatabaseLoader loader = new TimetableDatabaseLoader(model, currentSolution().getAssignment());
        loader.setCallback(callBack);
        iWorkThread = new Thread(loader);
        iWorkThread.start();
    }

    public Callback getLoadingDoneCallback() {
        return new LoadingDoneCallback();
    }

    public Callback getReloadingDoneCallback() {
        return new ReloadingDoneCallback();
    }

    public Callback getSavingDoneCallback() {
        return new SavingDoneCallback();
    }

    protected void afterSave() {
    }

    protected void afterLoad() {
    }

    protected void afterFinalSectioning() {
    }

    protected boolean useAmPm() {
        return getProperties().getPropertyBoolean("General.UseAmPm", true);
    }

    public class ReloadingDoneCallback implements Callback {
        Hashtable iCurrentAssignmentTable = new Hashtable();
        Hashtable iBestAssignmentTable = new Hashtable();
        Hashtable iInitialAssignmentTable = new Hashtable();
        String iSolutionId = null;
        Progress iProgress = null;

        public ReloadingDoneCallback() {
            iSolutionId = getProperties().getProperty("General.SolutionId");
            for (Lecture lecture : currentSolution().getModel().variables()) {
                Placement current = currentSolution().getAssignment().getValue(lecture);
                if (current != null)
                    iCurrentAssignmentTable.put(lecture.getClassId(), current);
                if (lecture.getBestAssignment() != null)
                    iBestAssignmentTable.put(lecture.getClassId(), lecture.getBestAssignment());
                if (lecture.getInitialAssignment() != null)
                    iInitialAssignmentTable.put(lecture.getClassId(), lecture.getInitialAssignment());
            }
        }

        private Lecture getLecture(Long classId) {
            for (Lecture lecture : currentSolution().getModel().variables()) {
                if (lecture.getClassId().equals(classId))
                    return lecture;
            }
            return null;
        }

        private Placement getPlacement(Lecture lecture, Placement placement) {
            TimeLocation time = null;
            for (TimeLocation t : lecture.timeLocations()) {
                if (placement.getTimeLocation().equals(t)) {
                    time = t;
                    break;
                }
            }
            if (time == null) {
                iProgress.warn("WARNING: Time " + placement.getTimeLocation().getLongName(useAmPm())
                        + " is no longer valid for class " + lecture.getName());
                return null;
            }
            Vector rooms = new Vector();
            for (RoomLocation r : lecture.roomLocations()) {
                if (placement.isMultiRoom() && placement.getRoomLocations().contains(r)) {
                    rooms.add(r);
                }
                if (!placement.isMultiRoom() && placement.getRoomLocation().equals(r)) {
                    rooms.add(r);
                    break;
                }
            }
            if (rooms.size() != lecture.getNrRooms()) {
                iProgress.warn("WARNING: Room(s) "
                        + (placement.isMultiRoom() ? placement.getRoomLocations().toString()
                                : placement.getRoomLocation().getName())
                        + " are no longer valid for class " + lecture.getName());
                return null;
            }
            return new Placement(lecture, time, rooms);
        }

        private void assign(Placement placement) {
            ((TimetableModel) currentSolution().getModel()).weaken(currentSolution().getAssignment(), placement);
            if (placement.isValid()) {
                Map<Constraint<Lecture, Placement>, Set<Placement>> conflictConstraints = currentSolution()
                        .getModel().conflictConstraints(currentSolution().getAssignment(), placement);
                if (conflictConstraints.isEmpty()) {
                    currentSolution().getAssignment().assign(0, placement);
                } else {
                    iProgress.warn("Unable to assign " + placement.variable().getName() + " &larr; "
                            + placement.getLongName(useAmPm()));
                    iProgress.warn("&nbsp;&nbsp;Reason:");
                    for (Constraint<Lecture, Placement> c : conflictConstraints.keySet()) {
                        Collection vals = (Collection) conflictConstraints.get(c);
                        for (Iterator j = vals.iterator(); j.hasNext();) {
                            Placement v = (Placement) j.next();
                            iProgress.warn("&nbsp;&nbsp;&nbsp;&nbsp;" + v.variable().getName() + " = "
                                    + v.getLongName(useAmPm()));
                        }
                        iProgress.debug("&nbsp;&nbsp;&nbsp;&nbsp;in constraint " + c);
                    }
                }
            } else {
                Lecture lecture = placement.variable();
                String reason = "";
                for (InstructorConstraint ic : lecture.getInstructorConstraints()) {
                    if (!ic.isAvailable(lecture, placement))
                        reason += "<br>&nbsp;&nbsp;&nbsp;&nbsp;instructor " + ic.getName() + " not available";
                }
                if (lecture.getNrRooms() > 0) {
                    if (placement.isMultiRoom()) {
                        for (RoomLocation roomLocation : placement.getRoomLocations()) {
                            if (!roomLocation.getRoomConstraint().isAvailable(lecture, placement.getTimeLocation(),
                                    lecture.getScheduler()))
                                reason += "<br>&nbsp;&nbsp;&nbsp;&nbsp;room " + roomLocation.getName()
                                        + " not available";
                        }
                    } else {
                        if (!placement.getRoomLocation().getRoomConstraint().isAvailable(lecture,
                                placement.getTimeLocation(), lecture.getScheduler()))
                            reason += "<br>&nbsp;&nbsp;&nbsp;&nbsp;room " + placement.getRoomLocation().getName()
                                    + " not available";
                    }
                }
                Map<Constraint<Lecture, Placement>, Set<Placement>> conflictConstraints = currentSolution()
                        .getModel().conflictConstraints(currentSolution().getAssignment(), placement);
                if (!conflictConstraints.isEmpty()) {
                    for (Constraint<Lecture, Placement> c : conflictConstraints.keySet()) {
                        Set<Placement> vals = conflictConstraints.get(c);
                        for (Placement p : vals) {
                            Lecture l = p.variable();
                            if (l.isCommitted())
                                reason += "<br>&nbsp;&nbsp;&nbsp;&nbsp;conflict with committed assignment "
                                        + l.getName() + " = " + p.getLongName(useAmPm()) + " (in constraint " + c
                                        + ")";
                            if (p.equals(placement))
                                reason += "<br>&nbsp;&nbsp;&nbsp;&nbsp;constraint " + c;
                        }
                    }
                }
                iProgress.warn("Unable to assign " + lecture.getName() + " &larr; "
                        + placement.getLongName(useAmPm()) + (reason.length() == 0 ? "." : ":" + reason));
            }
        }

        private void unassignAll() {
            for (Lecture lecture : currentSolution().getModel().variables())
                currentSolution().getAssignment().unassign(0, lecture);
        }

        public void execute() {
            iProgress = Progress.getInstance(currentSolution().getModel());

            if (!iBestAssignmentTable.isEmpty()) {
                iProgress.setPhase("Creating best assignment ...", iBestAssignmentTable.size());
                unassignAll();
                for (Iterator i = iBestAssignmentTable.entrySet().iterator(); i.hasNext();) {
                    Map.Entry entry = (Map.Entry) i.next();
                    iProgress.incProgress();
                    Lecture lecture = getLecture((Long) entry.getKey());
                    if (lecture == null)
                        continue;
                    Placement placement = getPlacement(lecture, (Placement) entry.getValue());
                    if (placement != null)
                        assign(placement);
                }

                currentSolution().saveBest();
            }
            if (!iInitialAssignmentTable.isEmpty()) {
                iProgress.setPhase("Creating initial assignment ...", iInitialAssignmentTable.size());
                for (Iterator i = iInitialAssignmentTable.entrySet().iterator(); i.hasNext();) {
                    Map.Entry entry = (Map.Entry) i.next();
                    iProgress.incProgress();
                    Lecture lecture = getLecture((Long) entry.getKey());
                    if (lecture == null)
                        continue;
                    Placement placement = getPlacement(lecture, (Placement) entry.getValue());
                    if (placement != null)
                        lecture.setInitialAssignment(placement);
                }
            }
            if (!iCurrentAssignmentTable.isEmpty()) {
                iProgress.setPhase("Creating current assignment ...", iCurrentAssignmentTable.size());
                unassignAll();
                for (Iterator i = iCurrentAssignmentTable.entrySet().iterator(); i.hasNext();) {
                    Map.Entry entry = (Map.Entry) i.next();
                    iProgress.incProgress();
                    Lecture lecture = getLecture((Long) entry.getKey());
                    if (lecture == null)
                        continue;
                    Placement placement = getPlacement(lecture, (Placement) entry.getValue());
                    if (placement != null)
                        assign(placement);
                }
            }
            iCurrentAssignmentTable.clear();
            iBestAssignmentRecords.clear();
            iInitialAssignmentTable.clear();
            iProgress = null;

            if (iSolutionId != null)
                getProperties().setProperty("General.SolutionId", iSolutionId);

            iLoadedDate = new Date();
            iWorking = false;
            afterLoad();
            Progress.getInstance(currentSolution().getModel()).setStatus("Awaiting commands ...");
        }
    }

    public class LoadingDoneCallback implements Callback {
        public void execute() {
            iLoadedDate = new Date();
            iWorking = false;
            afterLoad();
            Progress.getInstance(currentSolution().getModel()).setStatus("Awaiting commands ...");
            if (getProperties().getPropertyBoolean("General.StartSolver", false))
                start();
        }
    }

    public class SavingDoneCallback implements Callback {
        public void execute() {
            iWorking = false;
            afterSave();
            Progress.getInstance(currentSolution().getModel()).setStatus("Awaiting commands ...");
        }
    }

    public PropertiesInfo getGlobalInfo() {
        if (isPassivated())
            return iGlobalInfoBeforePassivation;
        Map<String, String> info = null;
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            info = super.currentSolution().getBestInfo();
            if (info == null)
                info = super.currentSolution().getInfo();
        } finally {
            lock.unlock();
        }
        PropertiesInfo globalInfo = new PropertiesInfo();
        for (Iterator i1 = info.entrySet().iterator(); i1.hasNext();) {
            Map.Entry entry = (Map.Entry) i1.next();
            String key = (String) entry.getKey();
            String value = entry.getValue().toString();
            globalInfo.setProperty(key, value);
        }
        return globalInfo;
    }

    public ConflictStatisticsInfo getCbsInfo() {
        ConflictStatistics cbs = null;
        for (Extension ext : getExtensions()) {
            if (ext instanceof ConflictStatistics) {
                cbs = (ConflictStatistics) ext;
                break;
            }
        }
        if (cbs == null || cbs.getNoGoods().isEmpty()) {
            if (iCbsInfo != null)
                return iCbsInfo;
            return null;
        }
        ConflictStatisticsInfo info = new ConflictStatisticsInfo();
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            info.load(this, cbs);
        } finally {
            lock.unlock();
        }
        return info;
    }

    public ConflictStatisticsInfo getCbsInfo(Long classId) {
        ConflictStatistics cbs = null;
        for (Extension ext : getExtensions()) {
            if (ext instanceof ConflictStatistics) {
                cbs = (ConflictStatistics) ext;
                break;
            }
        }
        if (cbs == null || cbs.getNoGoods().isEmpty()) {
            if (iCbsInfo != null)
                return iCbsInfo;
            return null;
        }
        ConflictStatisticsInfo info = new ConflictStatisticsInfo();
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            info.load(this, cbs, classId);
        } finally {
            lock.unlock();
        }
        return info;
    }

    public SolverUnassignedClassesModel getUnassignedClassesModel(String prefix) {
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            return new SolverUnassignedClassesModel(this, prefix);
        } finally {
            lock.unlock();
        }
    }

    private boolean match(Query q, final String name) {
        return q == null || q.match(new TermMatcher() {
            @Override
            public boolean match(String attr, String term) {
                if (term.isEmpty())
                    return true;
                if (attr == null) {
                    term: for (StringTokenizer s = new StringTokenizer(term, " ,"); s.hasMoreTokens();) {
                        String termToken = s.nextToken();
                        for (StringTokenizer t = new StringTokenizer(name, " ,"); t.hasMoreTokens();) {
                            String token = t.nextToken();
                            if (token.toLowerCase().startsWith(termToken.toLowerCase()))
                                continue term;
                        }
                        return false;
                    }
                    return true;
                } else if ("regex".equals(attr) || "regexp".equals(attr) || "re".equals(attr)) {
                    return name.matches(term);
                } else if ("find".equals(attr)) {
                    return name.toLowerCase().indexOf(term.toLowerCase()) >= 0;
                }
                return false;
            }
        });
    }

    private static enum Size {
        eq, lt, gt, le, ge
    };

    private boolean match(Query q, final RoomConstraint rc) {
        return q == null || q.match(new TermMatcher() {
            @Override
            public boolean match(String attr, String term) {
                if (term.isEmpty())
                    return true;
                if (attr == null) {
                    for (StringTokenizer s = new StringTokenizer(rc.getName(), " ,"); s.hasMoreTokens();) {
                        String token = s.nextToken();
                        if (term.equalsIgnoreCase(token))
                            return true;
                    }
                } else if ("regex".equals(attr) || "regexp".equals(attr) || "re".equals(attr)) {
                    return rc.getName().matches(term);
                } else if ("find".equals(attr)) {
                    return rc.getName().toLowerCase().indexOf(term.toLowerCase()) >= 0;
                } else if ("type".equals(attr) && rc.getType() != null) {
                    RoomType type = RoomTypeDAO.getInstance().get(rc.getType());
                    return type != null && (term.equalsIgnoreCase(type.getReference())
                            || term.equalsIgnoreCase(type.getLabel()));
                } else if ("size".equals(attr)) {
                    int min = 0, max = Integer.MAX_VALUE;
                    Size prefix = Size.eq;
                    String number = term;
                    if (number.startsWith("<=")) {
                        prefix = Size.le;
                        number = number.substring(2);
                    } else if (number.startsWith(">=")) {
                        prefix = Size.ge;
                        number = number.substring(2);
                    } else if (number.startsWith("<")) {
                        prefix = Size.lt;
                        number = number.substring(1);
                    } else if (number.startsWith(">")) {
                        prefix = Size.gt;
                        number = number.substring(1);
                    } else if (number.startsWith("=")) {
                        prefix = Size.eq;
                        number = number.substring(1);
                    }
                    try {
                        int a = Integer.parseInt(number);
                        switch (prefix) {
                        case eq:
                            min = max = a;
                            break; // = a
                        case le:
                            max = a;
                            break; // <= a
                        case ge:
                            min = a;
                            break; // >= a
                        case lt:
                            max = a - 1;
                            break; // < a
                        case gt:
                            min = a + 1;
                            break; // > a
                        }
                    } catch (NumberFormatException e) {
                    }
                    if (term.contains("..")) {
                        try {
                            String a = term.substring(0, term.indexOf('.'));
                            String b = term.substring(term.indexOf("..") + 2);
                            min = Integer.parseInt(a);
                            max = Integer.parseInt(b);
                        } catch (NumberFormatException e) {
                        }
                    }
                    return min <= rc.getCapacity() && rc.getCapacity() <= max;
                }
                return false;
            }
        });
    }

    public Vector getTimetableGridTables(TimetableGridContext context) {
        Vector models = new Vector();
        Query q = (context.getFilter() == null ? null : new Query(context.getFilter()));
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            TimetableModel model = (TimetableModel) currentSolution().getModel();
            switch (context.getResourceType()) {
            case TimetableGridModel.sResourceTypeRoom:
                for (RoomConstraint rc : model.getRoomConstraints()) {
                    if (!match(q, rc))
                        continue;
                    models.add(new SolverGridModel(this, rc, context));
                }
                break;
            case TimetableGridModel.sResourceTypeInstructor:
                for (InstructorConstraint ic : model.getInstructorConstraints()) {
                    if (!match(q, ic.getName()))
                        continue;
                    models.add(new SolverGridModel(this, ic, context));
                }
                break;
            case TimetableGridModel.sResourceTypeDepartment:
                for (DepartmentSpreadConstraint dc : model.getDepartmentSpreadConstraints()) {
                    if (!match(q, dc.getName()))
                        continue;
                    models.add(new SolverGridModel(this, dc, context));
                }
                if (model.getDepartmentSpreadConstraints().isEmpty()) {
                    org.cpsolver.ifs.assignment.Assignment<Lecture, Placement> assignment = currentSolution()
                            .getAssignment();
                    Map<Department, Set<Long>> dept2class = new HashMap<Department, Set<Long>>();
                    for (Object[] pair : (List<Object[]>) DepartmentDAO.getInstance().getSession().createQuery(
                            "select c.controllingDept, c.uniqueId from Class_ c where c.managingDept.solverGroup.uniqueId in :solverGroupIds")
                            .setParameterList("solverGroupIds", getOwnerId(), new LongType()).list()) {
                        Department dept = (Department) pair[0];
                        Long classId = (Long) pair[1];
                        Set<Long> classIds = dept2class.get(dept);
                        if (classIds == null) {
                            classIds = new HashSet<Long>();
                            dept2class.put(dept, classIds);
                        }
                        classIds.add(classId);
                    }
                    for (Department d : new TreeSet<Department>(dept2class.keySet())) {
                        if (!match(q, d.getShortLabel()))
                            continue;
                        Set<Long> classIds = dept2class.get(d);
                        int size = 0;
                        List<Placement> placements = new ArrayList<Placement>();
                        for (Lecture lecture : currentSolution().getModel().variables()) {
                            if (classIds.contains(lecture.getClassId())) {
                                size++;
                                Placement placement = assignment.getValue(lecture);
                                if (placement != null)
                                    placements.add(placement);
                            }
                        }
                        if (size > 0)
                            models.add(new SolverGridModel(this, TimetableGridModel.sResourceTypeDepartment,
                                    d.getUniqueId(), d.getShortLabel(), size, placements, context));
                    }
                }
                break;
            case TimetableGridModel.sResourceTypeCurriculum:
                Hashtable<String, List<Student>> curricula = new Hashtable<String, List<Student>>();
                boolean hasCurricula = false;
                HashSet<String> ignore = new HashSet<String>(), tested = new HashSet<String>();
                for (Student student : model.getAllStudents()) {
                    if (student.getCurriculum() != null && student.getAcademicClassification() != null) {
                        if (!hasCurricula) {
                            curricula.clear();
                            hasCurricula = true;
                        }
                        String c = student.getCurriculum() + " " + student.getAcademicClassification();
                        if (tested.add(c) && !match(q, c))
                            ignore.add(c);
                        if (ignore.contains(c))
                            continue;
                        List<Student> students = curricula.get(c);
                        if (students == null) {
                            students = new ArrayList<Student>();
                            curricula.put(c, students);
                        }
                        students.add(student);
                    } else if (!hasCurricula && student.getAcademicArea() != null
                            && student.getAcademicClassification() != null) {
                        String c = student.getAcademicArea()
                                + (student.getMajor() == null ? "" : " " + student.getMajor()) + " "
                                + student.getAcademicClassification();
                        if (tested.add(c) && !match(q, c))
                            ignore.add(c);
                        if (ignore.contains(c))
                            continue;
                        List<Student> students = curricula.get(c);
                        if (students == null) {
                            students = new ArrayList<Student>();
                            curricula.put(c, students);
                        }
                        students.add(student);
                    }
                }
                for (Map.Entry<String, List<Student>> curriculum : curricula.entrySet()) {
                    models.add(new SolverGridModel(this, curriculum.getKey(), curriculum.getValue(), context));
                }
                break;
            case TimetableGridModel.sResourceTypeSubjectArea:
                org.cpsolver.ifs.assignment.Assignment<Lecture, Placement> assignment = currentSolution()
                        .getAssignment();
                Map<SubjectArea, Set<Long>> sa2class = new HashMap<SubjectArea, Set<Long>>();
                for (Object[] pair : (List<Object[]>) DepartmentDAO.getInstance().getSession().createQuery(
                        "select co.subjectArea, c.uniqueId from Class_ c inner join c.schedulingSubpart.instrOfferingConfig.instructionalOffering.courseOfferings co where co.isControl = true and c.managingDept.solverGroup.uniqueId in :solverGroupIds")
                        .setParameterList("solverGroupIds", getOwnerId(), new LongType()).list()) {
                    SubjectArea sa = (SubjectArea) pair[0];
                    Long classId = (Long) pair[1];
                    Set<Long> classIds = sa2class.get(sa);
                    if (classIds == null) {
                        classIds = new HashSet<Long>();
                        sa2class.put(sa, classIds);
                    }
                    classIds.add(classId);
                }
                for (SubjectArea sa : new TreeSet<SubjectArea>(sa2class.keySet())) {
                    if (!match(q, sa.getSubjectAreaAbbreviation()))
                        continue;
                    Set<Long> classIds = sa2class.get(sa);
                    int size = 0;
                    List<Placement> placements = new ArrayList<Placement>();
                    for (Lecture lecture : currentSolution().getModel().variables()) {
                        if (classIds.contains(lecture.getClassId())) {
                            size++;
                            Placement placement = assignment.getValue(lecture);
                            if (placement != null)
                                placements.add(placement);
                        }
                    }
                    if (size > 0)
                        models.add(new SolverGridModel(this, TimetableGridModel.sResourceTypeSubjectArea,
                                sa.getUniqueId(), sa.getSubjectAreaAbbreviation(), size, placements, context));
                }
                break;
            }
        } finally {
            lock.unlock();
        }
        return models;
    }

    public ClassAssignmentDetails getClassAssignmentDetails(Long classId, boolean includeConstraints) {
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            TimetableModel model = (TimetableModel) currentSolution().getModel();
            for (Lecture lecture : model.variables()) {
                if (lecture.getClassId().equals(classId))
                    return new ClassAssignmentDetails(this, lecture, includeConstraints);
            }
            return null;
        } finally {
            lock.unlock();
        }
    }

    public Suggestions getSuggestions(SuggestionsModel model) {
        if (iWorking)
            return null;
        Lock lock = currentSolution().getLock().writeLock();
        lock.lock();
        try {
            return new Suggestions(this, model);
        } finally {
            lock.unlock();
        }
    }

    public AssignmentPreferenceInfo getInfo(Hint hint) {
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            return hint.getInfo(this);
        } finally {
            lock.unlock();
        }
    }

    public String getNotValidReason(Hint hint) {
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            return hint.getNotValidReason(this);
        } finally {
            lock.unlock();
        }
    }

    public void assign(Collection hints) {
        Lock lock = currentSolution().getLock().writeLock();
        lock.lock();
        try {
            Hashtable initialAssignments = new Hashtable();
            for (Placement placement : currentSolution().getAssignment().assignedValues()) {
                initialAssignments.put(placement.variable(), placement);
            }
            AssignmentRecord record = new AssignmentRecord(this);
            for (Iterator i = hints.iterator(); i.hasNext();) {
                Hint hint = (Hint) i.next();
                Placement p = hint.getPlacement((TimetableModel) currentSolution().getModel());
                if (p != null) {
                    Placement ini = (Placement) initialAssignments.get(p.variable());
                    record.add(ini, p);
                    Progress.getInstance(currentSolution().getModel())
                            .info(p.variable().getName() + ": "
                                    + (ini == null ? "not assigned" : ini.getLongName(useAmPm())) + " &rarr; "
                                    + p.getLongName(useAmPm()));
                    if (ini != null)
                        currentSolution().getAssignment().unassign(0, p.variable());
                } else if (hint.getDays() == 0) {
                    Lecture lecture = null;
                    for (Lecture l : currentSolution().getModel().variables())
                        if (l.getClassId().equals(hint.getClassId())) {
                            lecture = l;
                        }
                    if (lecture != null && !lecture.isCommitted())
                        currentSolution().getAssignment().unassign(0, lecture);
                }
            }
            for (Iterator i = hints.iterator(); i.hasNext();) {
                Hint hint = (Hint) i.next();
                Placement p = hint.getPlacement((TimetableModel) currentSolution().getModel());
                if (p != null)
                    currentSolution().getAssignment().assign(0, p);
            }
            for (Lecture lec : currentSolution().getModel()
                    .unassignedVariables(currentSolution().getAssignment())) {
                Placement p = (Placement) initialAssignments.get(lec);
                if (p != null) {
                    record.add(p, null);
                    Progress.getInstance(currentSolution().getModel()).info(
                            p.variable().getName() + ": " + p.getLongName(useAmPm()) + " &rarr; not assigned");
                }
            }
            record.done();
            iAssignmentRecords.addElement(record);
        } finally {
            lock.unlock();
        }
    }

    public Hashtable conflictInfo(Collection hints) {
        Hashtable conflictTable = new Hashtable();
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            HashSet done = new HashSet();
            for (Iterator i = hints.iterator(); i.hasNext();) {
                Hint hint = (Hint) i.next();
                Placement p = hint.getPlacement((TimetableModel) currentSolution().getModel());
                if (p == null)
                    continue;
                for (Constraint constraint : p.variable().hardConstraints()) {
                    HashSet conflicts = new HashSet();
                    constraint.computeConflicts(currentSolution().getAssignment(), p, conflicts);
                    if (conflicts != null && !conflicts.isEmpty()) {
                        for (Iterator j = conflicts.iterator(); j.hasNext();) {
                            Placement conflict = (Placement) j.next();
                            Hint confHint = new Hint(this, conflict);
                            if (done.contains(confHint))
                                continue;
                            if (!conflictTable.containsKey(confHint)) {
                                String name = constraint.getName();
                                if (constraint instanceof RoomConstraint) {
                                    name = "Room " + constraint.getName();
                                } else if (constraint instanceof InstructorConstraint) {
                                    name = "Instructor " + constraint.getName();
                                } else if (constraint instanceof GroupConstraint) {
                                    name = "Distribution " + constraint.getName();
                                } else if (constraint instanceof DepartmentSpreadConstraint) {
                                    name = "Balancing of department " + constraint.getName();
                                } else if (constraint instanceof SpreadConstraint) {
                                    name = "Same subpart spread " + constraint.getName();
                                } else if (constraint instanceof ClassLimitConstraint) {
                                    name = "Class limit " + constraint.getName();
                                }
                                conflictTable.put(confHint, name);
                            }
                        }
                    }
                }
                done.add(hint);
            }
        } finally {
            lock.unlock();
        }
        return conflictTable;
    }

    public Map<String, String> currentSolutionInfo() {
        if (isPassivated())
            return iCurrentSolutionInfoBeforePassivation;
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            return super.currentSolution().getExtendedInfo();
        } finally {
            lock.unlock();
        }
    }

    public Map<String, String> bestSolutionInfo() {
        if (isPassivated())
            return iBestSolutionInfoBeforePassivation;
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            return super.currentSolution().getBestInfo();
        } finally {
            lock.unlock();
        }
    }

    public void setProperties(DataProperties properties) {
        activateIfNeeded();
        this.getProperties().putAll(properties);
    }

    public boolean backup(File folder, String puid) {
        folder.mkdirs();
        if (currentSolution() == null)
            return false;
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            TimetableXMLSaver saver = new TimetableXMLSaver(this);
            File outXmlFile = new File(folder, puid + BackupFileFilter.sXmlExtension);
            File outPropertiesFile = new File(folder, puid + BackupFileFilter.sPropertiesExtension);
            try {
                saver.save(outXmlFile);
                FileOutputStream fos = null;
                try {
                    fos = new FileOutputStream(outPropertiesFile);
                    getProperties().store(fos, "Backup file");
                    fos.flush();
                    fos.close();
                    fos = null;
                } finally {
                    try {
                        if (fos != null)
                            fos.close();
                    } catch (IOException e) {
                    }
                }
                return true;
            } catch (Exception e) {
                sLog.error(e.getMessage(), e);
                if (outXmlFile.exists())
                    outXmlFile.delete();
                if (outPropertiesFile.exists())
                    outPropertiesFile.delete();
            }
        } finally {
            lock.unlock();
        }
        return false;
    }

    public boolean restore(File folder, String puid) {
        return restore(folder, puid, false);
    }

    public boolean restore(File folder, String puid, boolean removeFiles) {
        iAssignmentRecords.clear();
        iBestAssignmentRecords.clear();
        iCbsInfo = null;
        sLog.debug("restore(folder=" + folder + ",puid=" + puid + ")");
        File inXmlFile = new File(folder, puid + BackupFileFilter.sXmlExtension);
        File inPropertiesFile = new File(folder, puid + BackupFileFilter.sPropertiesExtension);

        TimetableModel model = null;
        try {
            if (isRunning())
                stopSolver();
            this.disposeNoInherit(false);
            FileInputStream fis = null;
            try {
                fis = new FileInputStream(inPropertiesFile);
                getProperties().load(fis);
            } finally {
                if (fis != null)
                    fis.close();
            }

            model = new TimetableModel(getProperties());
            Progress.getInstance(model).addProgressListener(new ProgressWriter(System.out));

            setInitalSolution(model);
            initSolver();

            TimetableXMLLoader loader = new TimetableXMLLoader(model, currentSolution().getAssignment());
            loader.setSolver(this);
            loader.setInputFile(inXmlFile);
            loader.load(currentSolution());
            loader.setSolver(null);

            Progress.getInstance(model).setStatus("Awaiting commands ...");

            if (removeFiles) {
                inXmlFile.delete();
                inPropertiesFile.delete();
            }

            return true;
        } catch (Exception e) {
            sLog.error(e.getMessage(), e);
            if (model != null)
                Progress.removeInstance(model);
        }

        return false;
    }

    public Long[] getOwnerId() {
        return getProperties().getPropertyLongArry("General.SolverGroupId", null);
    }

    private HashSet iDepartmentIds = null;

    public Set getDepartmentIds() {
        if (iDepartmentIds != null)
            return iDepartmentIds;
        iDepartmentIds = new HashSet();
        Long ownerId[] = getOwnerId();
        for (int i = 0; i < ownerId.length; i++) {
            SolverGroup sg = (new SolverGroupDAO()).get(ownerId[i]);
            for (Iterator j = sg.getDepartments().iterator(); j.hasNext();) {
                iDepartmentIds.add(((Department) j.next()).getUniqueId());
            }
        }
        return iDepartmentIds;
    }

    public Assignment getAssignment(Class_ clazz) throws Exception {
        Department dept = clazz.getManagingDept();
        if (dept != null && getDepartmentIds().contains(dept.getUniqueId()))
            return getAssignment(clazz.getUniqueId());
        return iCommitedClassAssignmentProxy.getAssignment(clazz);
    }

    public Assignment getAssignment(Long classId) {
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            Lecture lecture = null;
            for (Lecture l : currentSolution().getModel().variables()) {
                if (l.getClassId().equals(classId)) {
                    lecture = l;
                    break;
                }
            }
            if (lecture == null)
                return null;
            Placement placement = (Placement) currentSolution().getAssignment().getValue(lecture);
            if (placement == null)
                return null;
            Assignment assignment = new Assignment();
            assignment.setClassName(lecture.getName());
            assignment.setDays(new Integer(placement.getTimeLocation().getDayCode()));
            assignment.setStartSlot(new Integer(placement.getTimeLocation().getStartSlot()));
            if (placement.getTimeLocation().getDatePatternId() != null) {
                assignment.setDatePattern(
                        DatePatternDAO.getInstance().get(placement.getTimeLocation().getDatePatternId()));
            }
            assignment.setSlotsPerMtg(placement.getTimeLocation().getLength());
            assignment.setBreakTime(placement.getTimeLocation().getBreakTime());
            HashSet rooms = new HashSet();
            if (placement.isMultiRoom()) {
                for (RoomLocation r : placement.getRoomLocations()) {
                    Location room = (new LocationDAO()).get(r.getId());
                    if (room != null)
                        rooms.add(room);
                }
            } else {
                Location room = (new LocationDAO()).get(placement.getRoomLocation().getId());
                if (room != null)
                    rooms.add(room);
            }
            assignment.setRooms(rooms);
            TimePattern pattern = (new TimePatternDAO()).get(placement.getTimeLocation().getTimePatternId());
            assignment.setTimePattern(pattern);
            HashSet instructors = new HashSet();
            for (InstructorConstraint ic : lecture.getInstructorConstraints()) {
                DepartmentalInstructor instructor = null;
                if (ic.getResourceId() != null) {
                    instructor = (new DepartmentalInstructorDAO()).get(ic.getResourceId());
                }
                if (instructor != null)
                    instructors.add(instructor);

            }
            assignment.setInstructors(instructors);
            return assignment;
        } finally {
            lock.unlock();
        }
    }

    public AssignmentPreferenceInfo getAssignmentInfo(Class_ clazz) throws Exception {
        Department dept = clazz.getManagingDept();
        if (dept != null && getDepartmentIds().contains(dept.getUniqueId()))
            return getAssignmentInfo(clazz.getUniqueId());
        return iCommitedClassAssignmentProxy.getAssignmentInfo(clazz);
    }

    public AssignmentPreferenceInfo getAssignmentInfo(Long classId) {
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            Lecture lecture = null;
            for (Lecture l : currentSolution().getModel().variables()) {
                if (l.getClassId().equals(classId)) {
                    lecture = l;
                    break;
                }
            }
            if (lecture == null)
                return null;
            Placement placement = (Placement) currentSolution().getAssignment().getValue(lecture);
            if (placement == null)
                return null;
            return new AssignmentPreferenceInfo(this, placement);
        } finally {
            lock.unlock();
        }
    }

    public Hashtable getAssignmentTable(Collection classesOrClassIds) throws Exception {
        Hashtable assignments = new Hashtable();
        for (Iterator i = classesOrClassIds.iterator(); i.hasNext();) {
            Object classOrClassId = i.next();
            if (classOrClassId instanceof Object[])
                classOrClassId = ((Object[]) classOrClassId)[0];
            Assignment assignment = (classOrClassId instanceof Class_ ? getAssignment((Class_) classOrClassId)
                    : getAssignment((Long) classOrClassId));
            if (assignment != null)
                assignments.put(classOrClassId instanceof Class_ ? ((Class_) classOrClassId).getUniqueId()
                        : (Long) classOrClassId, assignment);
        }
        return assignments;
    }

    public Hashtable getAssignmentTable2(Collection classesOrClassIds) throws Exception {
        return getAssignmentTable(classesOrClassIds);
    }

    public Hashtable getAssignmentInfoTable(Collection classesOrClassIds) throws Exception {
        Hashtable infos = new Hashtable();
        for (Iterator i = classesOrClassIds.iterator(); i.hasNext();) {
            Object classOrClassId = i.next();
            if (classOrClassId instanceof Object[])
                classOrClassId = ((Object[]) classOrClassId)[0];
            AssignmentPreferenceInfo info = (classOrClassId instanceof Class_
                    ? getAssignmentInfo((Class_) classOrClassId)
                    : getAssignmentInfo((Long) classOrClassId));
            if (info != null)
                infos.put(classOrClassId instanceof Class_ ? ((Class_) classOrClassId).getUniqueId()
                        : (Long) classOrClassId, info);
        }
        return infos;
    }

    public Hashtable getAssignmentInfoTable2(Collection classesOrClassIds) throws Exception {
        return getAssignmentInfoTable(classesOrClassIds);
    }

    public Vector getAssignmentRecords() {
        return iAssignmentRecords;
    }

    public Vector getChangesToInitial() {
        Vector ret = new Vector();
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            for (Lecture lecture : currentSolution().getModel().variables()) {
                if (!ToolBox.equals(lecture.getInitialAssignment(),
                        currentSolution().getAssignment().getValue(lecture))) {
                    RecordedAssignment a = new RecordedAssignment(this, (Placement) lecture.getInitialAssignment(),
                            currentSolution().getAssignment().getValue(lecture));
                    if (lecture.getInitialAssignment() != null) {
                        a.getBefore().setDetails(new ClassAssignmentDetails(this, lecture,
                                (Placement) lecture.getInitialAssignment(), false));
                    }
                    if (currentSolution().getAssignment().getValue(lecture) != null) {
                        a.getAfter().setDetails(new ClassAssignmentDetails(this, lecture, false));
                    }
                    ret.addElement(a);
                }
            }
        } finally {
            lock.unlock();
        }
        return ret;
    }

    public Vector getAssignedClasses() {
        Vector ret = new Vector();
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            for (Lecture lecture : currentSolution().getAssignment().assignedVariables()) {
                ret.addElement(new ClassAssignmentDetails(this, lecture, false));
            }
        } finally {
            lock.unlock();
        }
        return ret;
    }

    public Vector getAssignedClasses(String prefix) {
        Vector ret = new Vector();
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            for (Lecture lecture : currentSolution().getAssignment().assignedVariables()) {
                if (prefix == null || lecture.getName().startsWith(prefix))
                    ret.addElement(new ClassAssignmentDetails(this, lecture, false));
            }
        } finally {
            lock.unlock();
        }
        return ret;
    }

    public Vector getChangesToBest() {
        Vector ret = new Vector();
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            for (Lecture lecture : currentSolution().getModel().variables()) {
                Placement placement = currentSolution().getAssignment().getValue(lecture);
                if (!ToolBox.equals(lecture.getBestAssignment(), placement)) {
                    RecordedAssignment a = new RecordedAssignment(this, (Placement) lecture.getBestAssignment(),
                            placement);
                    if (lecture.getBestAssignment() != null) {
                        a.getBefore().setDetails(new ClassAssignmentDetails(this, lecture,
                                (Placement) lecture.getBestAssignment(), false));
                    }
                    if (placement != null) {
                        a.getAfter().setDetails(new ClassAssignmentDetails(this, lecture, false));
                    }
                    ret.addElement(a);
                }
            }
        } finally {
            lock.unlock();
        }
        return ret;
    }

    public Vector getChangesToSolution(Long solutionId) throws Exception {
        return getChangesToSolution(solutionId, false);
    }

    public Vector getChangesToSolution(Long solutionId, boolean closeSession) throws Exception {
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            Session hibSession = (new SolutionDAO()).getSession();
            Transaction tx = null;
            Vector ret = new Vector();
            try {
                tx = hibSession.beginTransaction();
                Solution solution = (new SolutionDAO()).get(solutionId, hibSession);
                if (solution == null)
                    return null;
                Long ownerId = solution.getOwner().getUniqueId();
                Long[] ownerIds = getOwnerId();
                boolean sameOwner = false;
                for (int i = 0; i < ownerIds.length; i++)
                    if (ownerId.equals(ownerIds[i])) {
                        sameOwner = true;
                        break;
                    }
                if (!sameOwner)
                    return null;
                HashSet ids = new HashSet();
                for (Iterator i = solution.getAssignments().iterator(); i.hasNext();) {
                    Assignment assignment = (Assignment) i.next();
                    Lecture lecture = null;
                    for (Lecture l : currentSolution().getModel().variables()) {
                        if (l.getClassId().equals(assignment.getClassId())) {
                            lecture = l;
                            break;
                        }
                    }
                    ids.add(assignment.getClassId());
                    Placement placement = (lecture == null ? null
                            : currentSolution().getAssignment().getValue(lecture));
                    if (lecture == null || placement == null) {
                        RecordedAssignment a = new RecordedAssignment(this, assignment.getPlacement(), null);
                        a.getBefore().setDetails(
                                new ClassAssignmentDetails(solution, assignment, false, hibSession, null));
                        ret.addElement(a);
                    } else {
                        if (placement.equals(assignment.getPlacement()))
                            continue;
                        RecordedAssignment a = new RecordedAssignment(this, assignment.getPlacement(), placement);
                        a.getBefore().setDetails(
                                new ClassAssignmentDetails(solution, assignment, false, hibSession, null));
                        a.getAfter().setDetails(new ClassAssignmentDetails(this, lecture, false));
                        ret.addElement(a);
                    }
                }
                for (Lecture lecture : currentSolution().getModel().variables()) {
                    Placement placement = currentSolution().getAssignment().getValue(lecture);
                    if (ids.contains(lecture.getClassId()) || placement == null)
                        continue;
                    if (!ownerId.equals(lecture.getSolverGroupId()))
                        continue;
                    RecordedAssignment a = new RecordedAssignment(this, null, placement);
                    a.getAfter().setDetails(new ClassAssignmentDetails(this, lecture, false));
                    ret.addElement(a);
                }
                if (tx != null)
                    tx.commit();
            } catch (Exception e) {
                if (tx != null)
                    tx.rollback();
                throw e;
            } finally {
                //here we still need to close the session since it can be called by the remote solver as well
                if (closeSession && hibSession != null && hibSession.isOpen())
                    hibSession.close();
            }
            return ret;
        } finally {
            lock.unlock();
        }
    }

    public static class AssignmentRecord implements Serializable {
        private static final long serialVersionUID = 1L;
        private transient Solver iSolver;
        private Date iTimeStamp = new Date();
        private Suggestion iBefore = null, iAfter = null;
        private Vector iAssignments = new Vector();

        public AssignmentRecord() {
        }

        public AssignmentRecord(Solver solver) {
            iSolver = solver;
            iBefore = new Suggestion(iSolver);
        }

        public void done() {
            iAfter = new Suggestion(iSolver);
        }

        public void add(Placement before, Placement after) {
            iAssignments.add(new RecordedAssignment(iSolver, before, after));
        }

        public Date getTimeStamp() {
            return iTimeStamp;
        }

        public Suggestion getBefore() {
            return iBefore;
        }

        public Suggestion getAfter() {
            return iAfter;
        }

        public Vector getAssignments() {
            return iAssignments;
        }

        public String toString() {
            return "Record{TS=" + iTimeStamp + ", before=" + iBefore + ", after=" + iAfter + ", assignments="
                    + iAssignments.size() + "}";
        }

        public void toXml(Element element) throws Exception {
            if (iTimeStamp != null)
                element.addAttribute("timeStamp", String.valueOf(iTimeStamp.getTime()));
            if (iBefore != null)
                iBefore.toXml(element.addElement("before"));
            if (iAfter != null)
                iAfter.toXml(element.addElement("after"));
            if (iAssignments != null) {
                for (Enumeration e = iAssignments.elements(); e.hasMoreElements();) {
                    RecordedAssignment ra = (RecordedAssignment) e.nextElement();
                    ra.toXml(element.addElement("assignment"));
                }
            }
        }

        public static AssignmentRecord fromXml(Element element) throws Exception {
            AssignmentRecord r = new AssignmentRecord();
            if (element.attributeValue("timeStamp") != null)
                r.iTimeStamp = new Date(Long.parseLong(element.attributeValue("timeStamp")));
            if (element.element("before") != null)
                r.iBefore = Suggestion.fromXml(element.element("before"));
            if (element.element("after") != null)
                r.iAfter = Suggestion.fromXml(element.element("after"));
            for (Iterator i = element.elementIterator("assignment"); i.hasNext();) {
                r.iAssignments.add(RecordedAssignment.fromXml((Element) i.next()));
            }
            return r;
        }
    }

    public static class RecordedAssignment implements Serializable {
        private static final long serialVersionUID = 1L;
        private Hint iBefore = null, iAfter = null;

        public RecordedAssignment(Solver solver, Placement before, Placement after) {
            if (before != null)
                iBefore = new Hint(solver, before, false);
            if (after != null)
                iAfter = new Hint(solver, after, false);
        }

        public RecordedAssignment(Hint before, Hint after) {
            iBefore = before;
            iAfter = after;
        }

        public Hint getBefore() {
            return iBefore;
        }

        public Hint getAfter() {
            return iAfter;
        }

        public void toXml(Element element) {
            if (iBefore != null)
                iBefore.toXml(element.addElement("before"));
            if (iAfter != null)
                iAfter.toXml(element.addElement("after"));
        }

        public static RecordedAssignment fromXml(Element element) {
            Hint before = null, after = null;
            if (element.element("before") != null) {
                before = Hint.fromXml(element.element("before"));
            }
            if (element.element("after") != null) {
                after = Hint.fromXml(element.element("after"));
            }
            return new RecordedAssignment(before, after);
        }
    }

    public RoomReport getRoomReport(BitSet sessionDays, int startDayDayOfWeek, Long roomType, Float nrWeeks) {
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            return new RoomReport(this, sessionDays, startDayDayOfWeek, roomType, nrWeeks);
        } finally {
            lock.unlock();
        }
    }

    public DeptBalancingReport getDeptBalancingReport() {
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            return new DeptBalancingReport(this);
        } finally {
            lock.unlock();
        }
    }

    public ViolatedDistrPreferencesReport getViolatedDistrPreferencesReport() {
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            return new ViolatedDistrPreferencesReport(this);
        } finally {
            lock.unlock();
        }
    }

    public DiscouragedInstructorBtbReport getDiscouragedInstructorBtbReport() {
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            return new DiscouragedInstructorBtbReport(this);
        } finally {
            lock.unlock();
        }
    }

    public StudentConflictsReport getStudentConflictsReport() {
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            return new StudentConflictsReport(this);
        } finally {
            lock.unlock();
        }
    }

    public SameSubpartBalancingReport getSameSubpartBalancingReport() {
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            return new SameSubpartBalancingReport(this);
        } finally {
            lock.unlock();
        }
    }

    public PerturbationReport getPerturbationReport() {
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            return new PerturbationReport(this);
        } finally {
            lock.unlock();
        }
    }

    public void load(Element element) {
        try {
            iAssignmentRecords.clear();
            iBestAssignmentRecords.clear();
            Element assignmentRecords = element.element("assignmentRecords");
            if (assignmentRecords != null) {
                for (Iterator i = assignmentRecords.elementIterator("record"); i.hasNext();) {
                    iAssignmentRecords.add(AssignmentRecord.fromXml((Element) i.next()));
                }
            }
            Element bestAssignmentRecords = element.element("bestAssignmentRecords");
            if (bestAssignmentRecords != null) {
                for (Iterator i = bestAssignmentRecords.elementIterator("record"); i.hasNext();) {
                    iBestAssignmentRecords.add(AssignmentRecord.fromXml((Element) i.next()));
                }
            }
            if (element.element("cbsInfo") != null) {
                iCbsInfo = new ConflictStatisticsInfo();
                iCbsInfo.load(element.element("cbsInfo"));
            }
        } catch (Exception e) {
            sLog.error("Unable to load solver-related data, reson:" + e.getMessage(), e);
        }
    }

    public void save(Element element) {
        try {
            if (!iAssignmentRecords.isEmpty()) {
                Element assignmentRecords = element.addElement("assignmentRecords");
                for (Enumeration e = iAssignmentRecords.elements(); e.hasMoreElements();) {
                    AssignmentRecord r = (AssignmentRecord) e.nextElement();
                    r.toXml(assignmentRecords.addElement("record"));
                }
            }
            if (!iBestAssignmentRecords.isEmpty()) {
                Element bestAssignmentRecords = element.addElement("bestAssignmentRecords");
                for (Enumeration e = iBestAssignmentRecords.elements(); e.hasMoreElements();) {
                    AssignmentRecord r = (AssignmentRecord) e.nextElement();
                    r.toXml(bestAssignmentRecords.addElement("record"));
                }
            }
            ConflictStatisticsInfo cbsInfo = getCbsInfo();
            if (cbsInfo != null)
                cbsInfo.save(element.addElement("cbsInfo"));
        } catch (Exception e) {
            sLog.error("Unable to save solver-related data, reson:" + e.getMessage(), e);
        }
    }

    public CSVFile export(boolean useAmPm) {
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            CSVFile file = new CSVFile();
            file.setSeparator(",");
            file.setQuotationMark("\"");
            file.setHeader(new CSVField[] { new CSVField("COURSE"), new CSVField("ITYPE"), new CSVField("SECTION"),
                    new CSVField("SUFFIX"), new CSVField("DATE_PATTERN"), new CSVField("DAY"),
                    new CSVField("START_TIME"), new CSVField("END_TIME"), new CSVField("ROOM"),
                    new CSVField("INSTRUCTOR") });

            Vector lectures = new Vector(currentSolution().getModel().variables());
            Collections.sort(lectures);
            for (Iterator i = lectures.iterator(); i.hasNext();) {
                Lecture lecture = (Lecture) i.next();
                Placement placement = currentSolution().getAssignment().getValue(lecture);
                String name = lecture.getName();
                String itype = "";
                String section = "";
                if (name.indexOf(' ') >= 0) {
                    section = name.substring(name.lastIndexOf(' ') + 1);
                    name = name.substring(0, name.lastIndexOf(' ')).trim();
                }
                String suffix = "";
                while (section.charAt(section.length() - 1) >= 'a' && section.charAt(section.length() - 1) <= 'z') {
                    suffix = section.charAt(section.length() - 1) + suffix;
                    section = section.substring(0, section.length() - 1);
                }
                if (name.indexOf(' ') >= 0) {
                    itype = name.substring(name.lastIndexOf(' ') + 1);
                    name = name.substring(0, name.lastIndexOf(' ')).trim();
                }
                try {
                    Integer.parseInt(itype);
                    itype = name.substring(name.lastIndexOf(' ') + 1) + " " + itype;
                    name = name.substring(0, name.lastIndexOf(' ')).trim();
                } catch (NumberFormatException e) {
                }
                file.addLine(new CSVField[] { new CSVField(name), new CSVField(itype), new CSVField(section),
                        new CSVField(suffix),
                        new CSVField(placement == null ? "" : placement.getTimeLocation().getDatePatternName()),
                        new CSVField(placement == null ? "" : placement.getTimeLocation().getDayHeader()),
                        new CSVField(
                                placement == null ? "" : placement.getTimeLocation().getStartTimeHeader(useAmPm)),
                        new CSVField(
                                placement == null ? "" : placement.getTimeLocation().getEndTimeHeader(useAmPm)),
                        new CSVField(placement == null ? "" : placement.getRoomName(",")),
                        new CSVField(lecture.getInstructorName() == null ? "" : lecture.getInstructorName()) });
            }
            return file;
        } finally {
            lock.unlock();
        }
    }

    public synchronized boolean isPassivated() {
        return iIsPassivated;
    }

    public synchronized long timeFromLastUsed() {
        return System.currentTimeMillis() - iLastTimeStamp;
    }

    public synchronized boolean activateIfNeeded() {
        iLastTimeStamp = System.currentTimeMillis();
        if (!isPassivated())
            return false;
        sLog.debug("<activate " + iPassivationPuid + ">");

        iIsPassivated = false;

        System.gc();
        sLog.debug(" -- memory usage before activation:" + org.unitime.commons.Debug.getMem());
        restore(iPassivationFolder, iPassivationPuid, true);
        System.gc();
        sLog.debug(" -- memory usage after activation:" + org.unitime.commons.Debug.getMem());

        return true;
    }

    public synchronized boolean passivate(File folder, String puid) {
        if (isPassivated() || super.currentSolution() == null || super.currentSolution().getModel() == null)
            return false;
        sLog.debug("<passivate " + puid + ">");
        System.gc();
        sLog.debug(" -- memory usage before passivation:" + org.unitime.commons.Debug.getMem());
        iProgressBeforePassivation = getProgress();
        if (iProgressBeforePassivation != null)
            iProgressBeforePassivation.put("STATUS", "Pasivated");
        iGlobalInfoBeforePassivation = getGlobalInfo();
        iCurrentSolutionInfoBeforePassivation = currentSolutionInfo();
        iBestSolutionInfoBeforePassivation = bestSolutionInfo();

        iPassivationFolder = folder;
        iPassivationPuid = puid;
        backup(iPassivationFolder, iPassivationPuid);

        disposeNoInherit(false);
        System.gc();
        sLog.debug(" -- memory usage after passivation:" + org.unitime.commons.Debug.getMem());

        iIsPassivated = true;
        return true;
    }

    public synchronized boolean passivateIfNeeded(File folder, String puid) {
        long inactiveTimeToPassivate = 60000l * ApplicationProperty.SolverPasivationTime.intValue();
        if (isPassivated() || inactiveTimeToPassivate <= 0 || timeFromLastUsed() < inactiveTimeToPassivate
                || isWorking())
            return false;
        return passivate(folder, puid);
    }

    public Date getLastUsed() {
        return new Date(iLastTimeStamp);
    }

    public byte[] exportXml() throws Exception {
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            File temp = File.createTempFile(
                    "course-" + getProperties().getProperty("General.SolverGroupId", "").replace(',', '-'), ".xml");
            File conv = null;

            boolean anonymize = ApplicationProperty.SolverXMLExportNames.isFalse();
            boolean idconv = ApplicationProperty.SolverXMLExportConvertIds.isTrue();
            if (anonymize) {
                getProperties().setProperty("Xml.ConvertIds", idconv ? "true" : "false");
                getProperties().setProperty("Xml.ShowNames", "false");

                if (idconv) {
                    conv = File.createTempFile(
                            "idconv-" + getProperties().getProperty("General.SolverGroupId", "").replace(',', '-'),
                            ".xml");
                    getProperties().setProperty("Xml.IdConv", conv.getPath());
                    Document document = DocumentHelper.createDocument();
                    document.addElement("id-convertor");
                    FileOutputStream cos = new FileOutputStream(conv);
                    (new XMLWriter(cos, OutputFormat.createPrettyPrint())).write(document);
                    cos.flush();
                    cos.close();
                }
            }

            TimetableXMLSaver saver = new TimetableXMLSaver(this);
            ByteArrayOutputStream ret = new ByteArrayOutputStream();
            try {
                saver.save(temp);
                FileInputStream fis = new FileInputStream(temp);
                byte[] buf = new byte[16 * 1024];
                int read = 0;
                while ((read = fis.read(buf, 0, buf.length)) > 0)
                    ret.write(buf, 0, read);
                ret.flush();
                ret.close();
                fis.close();
            } catch (Exception e) {
                sLog.error(e.getMessage(), e);
            }

            temp.delete();
            if (conv != null)
                conv.delete();

            if (anonymize) {
                getProperties().setProperty("Xml.ConvertIds", "false");
                getProperties().setProperty("Xml.ShowNames", "true");
                getProperties().remove("Xml.IdConv");
            }

            return ret.toByteArray();
        } finally {
            lock.unlock();
        }
    }

    public void interrupt() {
        try {
            if (iSolverThread != null) {
                iStop = true;
                if (iSolverThread.isAlive() && !iSolverThread.isInterrupted())
                    iSolverThread.interrupt();
            }
            if (iWorkThread != null && iWorkThread.isAlive() && !iWorkThread.isInterrupted()) {
                iWorkThread.interrupt();
            }
        } catch (Exception e) {
            sLog.error("Unable to interrupt the solver, reason: " + e.getMessage(), e);
        }
    }

    public Map<String, String> statusSolutionInfo() {
        if (isPassivated())
            return (iBestSolutionInfoBeforePassivation == null ? iCurrentSolutionInfoBeforePassivation
                    : iBestSolutionInfoBeforePassivation);
        Lock lock = currentSolution().getLock().readLock();
        lock.lock();
        try {
            Map<String, String> info = super.currentSolution().getBestInfo();
            try {
                org.cpsolver.ifs.solution.Solution<Lecture, Placement> solution = getWorkingSolution();
                if (info == null || getSolutionComparator().isBetterThanBestSolution(solution))
                    info = solution.getModel().getInfo(solution.getAssignment());
            } catch (ConcurrentModificationException e) {
            }
            return info;
        } finally {
            lock.unlock();
        }
    }

    public static interface SolverDisposeListener {
        public void onDispose();
    }

    public Object exec(Object[] cmd) throws Exception {
        Class[] types = new Class[(cmd.length - 2) / 2];
        Object[] args = new Object[(cmd.length - 2) / 2];
        for (int i = 0; i < types.length; i++) {
            types[i] = (Class) cmd[2 * i + 2];
            args[i] = cmd[2 * i + 3];
        }

        return getClass().getMethod((String) cmd[0], types).invoke(this, args);
    }

    public String getHost() {
        return "local";
    }

    public String getUser() {
        return getProperties().getProperty("General.OwnerPuid");
    }

    @Override
    public boolean hasFinalSectioning() {
        return ((TimetableModel) currentSolution().getModel()).getStudentSectioning().hasFinalSectioning();
    }

    @Override
    public boolean hasConflicts(Long offeringId) throws Exception {
        return false;
    }

    @Override
    public Set<Assignment> getConflicts(Long classId) throws Exception {
        return null;
    }

    @Override
    public Set<TimeBlock> getConflictingTimeBlocks(Long classId) throws Exception {
        return null;
    }
}