/********************************************************
* Copyright (C) 2008 Course Scheduler Team
*
* This program is free software; you can redistribute it and/or modify it under the terms of
* the GNU General Public License as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to:
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA
********************************************************/
/********************************************************
* Course Scheduler
* File: BuildAssistThread.java
*
* Contains class:
*
* BuildAssistThread:
*
* Purpose: To assist in building schedules
*
* @author Mike Reinhold
********************************************************/
package Scheduler; //declare as member of scheduler package
/********************************************************
* The following imports are necessary for this class
********************************************************/
import javax.swing.SwingWorker; //subclasses by this class
import org.slf4j.ext.XLogger;
import org.slf4j.ext.XLoggerFactory;
import java.util.ArrayList; //use to store list of helper threads
import java.util.HashMap;
import java.util.List;
import java.util.Vector;
/********************************************************
* Class BuildAssistThread
*
* @purpose Assist in the building of schedules by threading
*
* @see SwingWorker<Void,Void>
********************************************************/
public class BuildAssistThread extends SwingWorker<Void,Void> {
/**
* Static logger
*/
private static XLogger logger = XLoggerFactory.getXLogger(BuildAssistThread.class);
/********************************************************
* The following are protected static constants for versioning
********************************************************/
protected final static long versionID = 2009021100023L;//file version
/********************************************************
* The following are private fields for the class
********************************************************/
private ArrayList<int[][]> greyCodes; //the list of grey codes for this thread to test
private ScheduleVector result; //the possible schedules found
private String term; //the term for the schedules
private Course[] possible; //the courses to use
private boolean allowClosed; //if closed courses are allowd
private int useMin; //the min number of courses to use
private int permute; //the number of combinations of schedules
private ThreadSynch sync; //the thread sync object
private int[] numberSelected; //the number of each section selected
private boolean reportingEnabled; //if conflict reporting is enabled
@Override
protected Void doInBackground() throws Exception {
for(int[][]comb: greyCodes){
if(sync.isCanceled()){ //check if the operation is cancelled
sync.allowUpdate = false; //disallow updating the monitor
sync.removeHelper(this); //remove this helper from the list of helpers
return null; //return null as Void
}
Schedule item = new Schedule(term);
Vector<Section> notUsed = new Vector<Section>();
for(int course = 0; course < possible.length; course++){
for(int toAdd: comb[course]){
try{
if(comb[course][comb[course].length-1] != possible[course].getNumOfSections()){
Section add = possible[course].getSection(toAdd);
if(!item.add(add, allowClosed)){
notUsed.add(add);
}
}
}
catch(Exception ex){}
}
}
if (item.numberSections() >= useMin && item.allPrimaryUsed(sync.getPrimary()) && item.hasAllLinks(sync.getOwner().dependancy) && item.allFitTypes(sync.getType()) && item.hasAllSections(numberSelected, possible)){//verify that the minimum number of courses is satisfied
item.reRate(); //rate the schedule before adding it
result.addIfNew(item); //if meets min requirement and not been added, add
} //to list of valid results
else if(reportingEnabled){
synchronized(BuildAssistThread.class){
boolean valid = true;
HashMap<String,Integer> used = new HashMap<String, Integer>();
for(Course course: possible){
used.put(course.getPerceivedCourse(), new Integer(0));
}
for(int combLoc = 0; combLoc < comb.length; combLoc++){
for(int greyLoc = 0; greyLoc < comb[combLoc].length; greyLoc++){
try{
if(comb[combLoc][greyLoc] != possible[combLoc].getNumOfSections()){
int inc = used.remove(possible[combLoc].getPerceivedCourse()).intValue() + 1;
used.put(possible[combLoc].getPerceivedCourse(), new Integer(inc));
}
}
catch(Exception ex){}
}
}
int avail = 0;
for(String key: used.keySet()){
avail += used.get(key).intValue();
}
if(useMin > avail){
valid = false;
}
for(String course: sync.getPrimary()){
if(used.get(course).intValue() < 1){
valid = false;
}
}
for(int pos = 0; pos < numberSelected.length; pos++){
int times = used.get(possible[pos].getPerceivedCourse()).intValue();
if(times != 0 && times != numberSelected[pos]){
valid = false;
}
}
for(CourseList link: sync.getOwner().dependancy){
boolean oneContained = false;
boolean allContained = true;
for(String course: link){
if(used.get(course).intValue() > 0){
oneContained = true;
}
else{
allContained = false;
}
}
if(oneContained && !allContained){
valid = false;
}
}
for(int combLoc = 0; combLoc < comb.length; combLoc++){
for(int greyLoc = 0; greyLoc < comb[combLoc].length; greyLoc++){
try{
Section check = possible[combLoc].getSection(comb[combLoc][greyLoc]);
if(!check.fitsType(sync.getType())){
valid = false;
}
}
catch (Exception ex){}
}
}
if(valid){
Conflict conflict = new Conflict();
conflict.setSchedule(item);
String combStr = new String("Schedule combonation: ");
for(int combLoc = 0; combLoc < comb.length; combLoc++){
for(int greyLoc = 0; greyLoc < comb[combLoc].length; greyLoc++){
try{
combStr += possible[combLoc].getSection(comb[combLoc][greyLoc]) + " ";
}
catch (Exception ex){}
}
}
conflict.setScheduleCombonationDesc(combStr);
String contStr = new String("Schedule contains: ");
for(Section sec: item.getClassesObj()){
contStr += sec + " ";
}
conflict.setScheduleContents(contStr);
if(useMin > item.numberSections()){
conflict.setCountMessage("Does not contain the minimum number of sections");
}
HashMap<String, Integer> numUsed = new HashMap<String, Integer>();
for(Course course: possible){
numUsed.put(course.getPerceivedCourse(), new Integer(0));
}
for(Section section: item.getClassesObj()){
int contain = numUsed.remove(section.getPerceivedCourse()).intValue() + 1;
numUsed.put(section.getPerceivedCourse(), new Integer(contain));
}
for(Section sec: notUsed){
if(sync.getPrimary().contains(sec.getPerceivedCourse()) && numUsed.get(sec.getCourseID()).intValue() < 1){
conflict.addPrimary(sec, item.findConflictingSection(sec));
}
}
for(int loc = 0; loc < numberSelected.length; loc++){
Course course = possible[loc];
int contained = numUsed.get(course.getPerceivedCourse()).intValue();
if(contained != 0 && contained != numberSelected[loc]){
for(int index: comb[loc]){
Section missing = possible[loc].getSection(index);
if(notUsed.contains(missing)){
conflict.addNumberError(missing, item.findConflictingSection(missing), numberSelected[loc]);
}
}
}
}
HashMap<String, Integer> combPos = new HashMap<String, Integer>();
for(int loc = 0; loc < possible.length; loc++){
combPos.put(possible[loc].getPerceivedCourse(), new Integer(loc));
}
Vector<CourseList> usedLinks = new Vector<CourseList>();
for(Section found: item.getClassesObj()){
for(CourseList link: sync.getOwner().dependancy){
if(usedLinks.contains(link)){
continue;
}
if(link.contains(found.getPerceivedCourse())){
boolean firstIn = true, shouldWrite = false;
String inCourses = new String();
Vector<Section[]> conf = new Vector<Section[]>();
for(String other: link){
if(numUsed.get(other).intValue() < 1){
usedLinks.add(link);
shouldWrite = true;
int loc = combPos.get(other).intValue();
for(int secIndex: comb[loc]){
Section missing = possible[loc].getSection(secIndex);
if(!item.contains(missing)){
conf.add(new Section[]{missing, item.findConflictingSection(missing)});
}
}
}
else{
inCourses += (firstIn ? "Missing Course Link: " : ", ") + other;
firstIn = false;
}
}
if(shouldWrite){
conflict.addLinkError(inCourses, conf, null);
}
}
}
}
for(Section not: notUsed){
if(!conflict.hasConflict(not)){
conflict.addMinUseError(not, item.findConflictingSection(not));
}
}
if(Main.conflictDebugEnabled){
logger.debug(conflict.toTerminalString());
}
sync.addConflict(conflict);
}
}
}
if (sync.allowUpdate){ //check if progress monitor can be updated
sync.incrementProgressValue();//increment the progress synchronously
}
publish((Void)null);
}
return null;
}
@Override
protected void done(){
sync.removeHelper(this); //remove this helper from the list
}
@Override
protected void process(List<Void> l){
sync.updateProgressValue();
}
public ArrayList<int[][]> getGreyCodes() {
return greyCodes;
}
public void setGreyCodes(ArrayList<int[][]> greyCodes) {
this.greyCodes = greyCodes;
}
public ScheduleVector getResult() {
return result;
}
public void setResult(ScheduleVector result) {
this.result = result;
}
public String getTerm() {
return term;
}
public void setTerm(String term) {
this.term = term;
}
public Course[] getPossible() {
return possible;
}
public void setPossible(Course[] possible) {
this.possible = possible;
}
public boolean isAllowClosed() {
return allowClosed;
}
public void setAllowClosed(boolean allowClosed) {
this.allowClosed = allowClosed;
}
public int getUseMin() {
return useMin;
}
public void setUseMin(int useMin) {
this.useMin = useMin;
}
public int getPermute() {
return permute;
}
public void setPermute(int permute) {
this.permute = permute;
}
public ThreadSynch getSync() {
return sync;
}
public void setSync(ThreadSynch sync) {
this.sync = sync;
}
public int[] getNumberSelected() {
return numberSelected;
}
public void setNumberSelected(int[] numberSelected) {
this.numberSelected = numberSelected;
}
public boolean isReportingEnabled() {
return reportingEnabled;
}
public void setReportingEnabled(boolean reportingEnabled) {
this.reportingEnabled = reportingEnabled;
}
}