/******************************************************** * 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: Database.java * * Contains class: * * Database: * * Purpose: To store Prof and Course information * for the creation of courses * * @author Mike Reinhold *********************************************************/ package Scheduler; //declare as a member of scheduler package /******************************************************** * Import the necessary classes to support the database *********************************************************/ import io.devyse.scheduler.analytics.keen.KeenEngine; import java.io.Serializable; //import serializable interface import java.util.Arrays; //import array util class import java.util.Calendar; //import java calendar utility import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; //import tree map for database import java.util.Vector; import javax.swing.JOptionPane; import javax.swing.ProgressMonitor; //import the progress bar import org.slf4j.ext.XLogger; import org.slf4j.ext.XLoggerFactory; import java.util.ArrayList; //import the arrayList utility /******************************************************** * Class: Database * * @purpose Provides storage and methods for making class schedules *********************************************************/ public class Database implements Serializable, Cloneable{ /** * Static logger */ private static XLogger logger = XLoggerFactory.getXLogger(Database.class); /******************************************************** * The following are fields of the Database *********************************************************/ private TreeMap<String, Course> database; //holds the courses for the selected term private String term; //specifies the database's term private Calendar creation; //specifies when the database was created private ProfDatabase profs; //database of profs and ratings private boolean undergrad; //flag for if the database has undergrad info private boolean gradCampus; //flag for if the database has on campus grad info private boolean gradDist; //flag for if the database has off campus grad info /******************************************************** * The following are static constants for use within the package *********************************************************/ protected final static int empty = 0; //value for empty protected final static int beginIndex = 0; //starting index for loops /******************************************************** * UPDATE SERIAL VERSION IN VERSION WHEN THIS FILE CHANGES ********************************************************/ protected static final long versionID = 2008112500092L; //object version protected static final long serialVersionUID = 10L + Version.database.id;//serial ID /******************************************************** * (Constructor) * * @purpose Creates a new database and initializes storage *********************************************************/ public Database(boolean hasRatings){ database = new TreeMap<String, Course>(); //create space for the arraylist database term = new String(); //create new string for the term creation = Calendar.getInstance(); //create new date at current time profs = new ProfDatabase(hasRatings); //create new tree set for profs undergrad = false; //make sure flags are false to start gradCampus = false; //make sure flags are false to start gradDist = false; //make sure flags are false to start } /******************************************************** * (Constructor * * @purpose Creates a database based on another database * * @param Database other: the database to use fro creating a new database *********************************************************/ public Database(Database other){ this.setTerm(new String(other.getTerm())); //set the term this.setProfs(other.getProfs()); //set the profs this.setCreation(other.getCreation()); //set the creation date this.setDatabase(other.getDatabase()); //set the course database } /******************************************************** * @purpose Adds a section to the database * * @param Section newSection: the section to be added to the * database *********************************************************/ public void addSection(Section newSection){ int end = database.size(); //get the database's size if (end == empty){ //check if database is empty Course newCourse = new Course(newSection);//if so make new course for section database.put(newCourse.getPerceivedCourse(), newCourse);//add to database } else { Course check = database.get(newSection.getPerceivedCourse());//get course if exists if(check == null){ //check for existing course Course newCourse = new Course(newSection);//if not, make new course database.put(newCourse.getPerceivedCourse(), newCourse);//add new course } else{ check.addSection(newSection); //else add to course } } //TODO handle section instructor lists List<String> instructorList = newSection.getInstructorList(); ProfDatabase profDB = this.getProfs(); for(String instructor: instructorList){ Prof prof = profDB.get(instructor); if(prof == null) { prof = new Prof(); prof.setName(instructor); profDB.addIfNew(prof); } newSection.setInstructor(prof); } setDatabaseFlags(newSection); } /******************************************************** * @purpose Sets the database flags * * @param Section item: the section whose flags should be 'OR'ed * with the database flags *********************************************************/ protected void setDatabaseFlags(Section item){ undergrad |= item.fitsType(CourseType.undergrad); gradCampus |= item.fitsType(CourseType.campusGrad); gradDist |= item.fitsType(CourseType.distanceGrad); } /******************************************************** * @purpose Sets the database directly * * @param TreeMap<String, Course> data: the data to set to *********************************************************/ protected void setDatabase(TreeMap<String, Course> data){ this.database = data; //set the database } /******************************************************** * @purpose Gets the database in native data structure * * @return TreeMap<String, Course>: the data to set to *********************************************************/ protected TreeMap<String, Course> getDatabase(){ return this.database; //return the course database } /******************************************************** * @purpose Returns if the database is empty * * @return boolean: if the database is empty *********************************************************/ public boolean isEmpty(){ return (this.database.isEmpty()); //return if the database is empty } /******************************************************** * @purpose Calculates the schedules * * @return Schedule[]: the possible schedules based on parameters *********************************************************/ public Schedule[] makeSchedulesOpt(String[] classes, ArrayList<String> primary, boolean allowClosed, int useMin, ThreadSynch sync, boolean[][] sectionsAllowed, int[] numberSelected, boolean reportingEnabled){ long startTime = System.currentTimeMillis(); int[] permOffset = new int[classes.length]; //combinatorial object offset currently 0, may need to be 1; must test to find out if all combinations are found or not for(int pos = beginIndex; pos < permOffset.length; pos++){ permOffset[pos] = (primary.contains(classes[pos])) ? 0 : 1; } ScheduleVector result = new ScheduleVector();//create list for schedules Course[] possible = new Course[classes.length];//space for possible courses int permute = 1; //min number of permutations int count = 0; ProgressMonitor tempMon = sync.getWatch(); //get the watch tempMon.setMillisToDecideToPopup(100); //set to decide to popup right away tempMon.setMillisToPopup(100); //set to popup right away sync.setWatch(tempMon); //set the monitor sync.updateWatch("Calculating Combinations", 1);//update the monitor int pos = beginIndex; try{ for(; pos < classes.length && permute > 0; pos++){ possible[pos] = this.database.get(classes[pos]);//add the course permute *= possible[pos].getNumOfSections() + permOffset[pos];//get permutations } } catch(NullPointerException ex){ JOptionPane.showMessageDialog(sync.getOwner(), "Unable to create schedules for the selected classes because " + classes[pos] + " does not exist.\n " + "Building schedules without " + classes[pos] + ".", "Unable to Build Schedules", JOptionPane.ERROR_MESSAGE); sync.getOwner().scheduleClassModel.removeElement(classes[pos]); sync.getOwner().scheduleClassList.setModel(sync.getOwner().scheduleClassModel); sync.failed = true; return new Schedule[0]; } if(permute <= 0){ //check if valid permute sync.closeWatch(); //close the watch return null; //return null to thread } sync.setPermute(permute); boolean plural = false; for(int value: numberSelected){ plural |= (value > 1); } int codesPerThread = permute/Main.availProcs;//find maximum number of grey codes per thread based on 1 thread/logical core + master thread Main.prefs.setGreyCodeLimit((codesPerThread < 20) ? 20 : codesPerThread);//set the number of grey codes to the optimum value over 20 sync.getWatch().setMaximum(permute + 1); //set maximum value for progress bar CombinationGenerator[] generate = new CombinationGenerator[possible.length]; for(int col = 0; col < generate.length; col++){ generate[col] = new CombinationGenerator(possible[col].getNumOfSections() + 1, numberSelected[col]); } ArrayList<int[][]> greyCodes = new ArrayList<int[][]>(); int[][] comb = new int[possible.length][]; boolean[] roll = new boolean[possible.length]; boolean[] next = roll.clone(); for(int col = 0; col < generate.length; col++){ comb[col] = generate[col].getNext().clone(); } boolean toStop = false; major: while(true){ for(int col = 0; col < generate.length; col++){ if(roll[col]){ if(!generate[col].hasMore()){ if(col == generate.length - 1){ toStop = true; } else{ roll[col + 1] = true; } generate[col].reset(); } next[col] = false; comb[col] = generate[col].getNext().clone(); } } next[0] = true; roll = next.clone(); for(int spot = 0; spot < comb.length; spot++){ for(int sec = 0; sec < comb[spot].length; sec++){ try{ if(!sectionsAllowed[spot][comb[spot][sec]]){ continue major; } } catch(IndexOutOfBoundsException ex){} catch(NullPointerException ex1){} } } if(greyCodes.size() == Main.prefs.getGreyCodeLimit() - 1 || toStop){ BuildAssistThread helper = new BuildAssistThread();//create thread helper.setGreyCodes(greyCodes); //set the grey codes helper.setAllowClosed(allowClosed); //allow closed courses helper.setPermute(permute); //set total number of threads helper.setResult(result); //set the result object helper.setPossible(possible); //set the possible courses helper.setUseMin(useMin); //set the min use value helper.setSync(sync); //set the sync object helper.setReportingEnabled(reportingEnabled);//set reporting enabled helper.setNumberSelected(numberSelected);//set the number of each section selected sync.addHelper(helper); //add the helper Main.threadExec.execute(helper); //execute the helper count += greyCodes.size(); if(count > permute){ sync.setPermute(count); } greyCodes = new ArrayList<int[][]>(); } else{ greyCodes.add(comb.clone()); } if(toStop){ break; } } while(sync.hasLivingHelpers()){ //check if threads still alive if(sync.isCanceled()){ //check if cancelled sync.cancel(); //cancel thread via the sync object sync.finished = permute; //set finished to permute } } if(sync.isCanceled()){ //check if cancelled return null; //return null to master thread } Schedule[] temp = result.toArray(new Schedule[0]);//get the schedule[] Arrays.sort(temp); //sort it long endTime = System.currentTimeMillis(); registerScheduleEvent(allowClosed, useMin, reportingEnabled, classes, primary, sectionsAllowed, numberSelected, sync, temp, endTime - startTime); sync.updateWatch("Updating List", -1); //set new note return temp; //return the results } private void registerScheduleEvent(boolean allowClosed, int useMin, boolean reportingEnabled, String[] classes, List<String> primary, boolean[][] sectionsAllowed, int[] numberSelected, ThreadSynch sync, Schedule[] temp, long runtime){ try{ if(!Main.prefs.isAnalyticsOptOut()){ Map<String, Object> event = new HashMap<>(); event.put("university.name", "Kettering University"); event.put("university.term", this.getTerm()); event.put("parameters.closed", allowClosed); event.put("parameters.use.min", useMin); event.put("parameters.use.all", sync.getOwner().useAll.isSelected()); event.put("parameters.reporting", reportingEnabled); event.put("parameters.classes", classes); event.put("parameters.primary", primary); event.put("results.schedules.found", temp.length); event.put("results.schedules.conflicts", sync.getConflicts().size()); event.put("results.runtime", runtime); buildAllowed(event, classes, sectionsAllowed); buildSelected(event, classes, numberSelected); buildLinked(event, sync); KeenEngine.getDefaultKeenEngine().registerEvent(Main.KEEN_SCHEDULE, event); } }catch(Exception e){ logger.error("Exception processing schedule event", e); } } private void buildSelected(Map<String, Object> event, String[] classes, int[] numberSelected){ for(int classPos = 0; classPos < classes.length; classPos++){ event.put("parameters.selected."+classes[classPos],numberSelected[classPos]); } } private void buildAllowed(Map<String, Object> event, String[] classes, boolean[][] sectionsAllowed){ for(int classPos = 0; classPos < classes.length; classPos++){ Course course = this.getCourse(classes[classPos]); Section[] sections = course.getSectionsSObj(); for(int sectionPos = 0; sectionPos < sections.length; sectionPos++){ event.put("parameters.allowed."+classes[classPos]+"."+sections[sectionPos].getSection(), sectionsAllowed[classPos][sectionPos]); } } } private void buildLinked(Map<String, Object> event, ThreadSynch sync){ event.put("parameters.links", sync.getOwner().dependancy); } /******************************************************** * @purpose returns the database's term * * @return the term this database represents *********************************************************/ public String getTerm() { return term; //return the term } /******************************************************** * @purpose Set the database's term to the specified string * * @param String term: the string to set as the Term *********************************************************/ public void setTerm(String term) { this.term = term; //set the term } /******************************************************** * @purpose Return the date of creation * * @return Calendar: the date of creation of the database *********************************************************/ public Calendar getCreation() { return creation; //return the date } /******************************************************** * @purpose Sets the creation date of the database * * @param Calendar creation: the date to set the creation to *********************************************************/ public void setCreation(Calendar creation) { this.creation = creation; //set the date } /******************************************************** * @purpuse Return a string representation of the database * * @return String: the database represented as a string in form * of Semester Year eg. Summer 2008 *********************************************************/ @Override public String toString(){ int value = Integer.parseInt(this.term); //parse the term value int year = value / (100); //extract the year int term = value - (year * 100); //extract the term return new String(Term.getTerm(term).toString() + " " + Integer.toString(year)); //return the string } /******************************************************** * @purpose Saves the database to a file name based on term * and return if successful * * @return booleam: if the save was successful *********************************************************/ public boolean save(){ return Serial.save(Main.dataFolder + this.getTerm() + Main.databaseExt, this);//return the result of saving the database } /******************************************************** * @purpose Loads the database from a file name * * @return Database: the database deserialized from the file *********************************************************/ public static Database load(String term){ return Serial.load(Main.dataFolder + term + Main.databaseExt);//return the loaded database } /******************************************************** * @purpose Forces the database to rerate all sections *********************************************************/ public void reRate(){ for(String key: this.database.keySet()){ //for each key this.database.get(key).reRate(); //get the course and rerate } } /******************************************************** * @purpose Return if the database should redownload from banner * based on the creation date * * @return boolean: if the database should update *********************************************************/ public boolean shouldUpdate(){ Calendar today = Calendar.getInstance(); //get current date if (today.after(this.creation)){ //check agains creation date int currentDay = today.get(Calendar.DAY_OF_YEAR); int createdDay = this.creation.get(Calendar.DAY_OF_YEAR); createdDay = (createdDay - currentDay > 0) ? (367 - createdDay) : createdDay;//get difference if (Math.abs(currentDay - createdDay) > Main.prefs.getUpdateMin()){ return true; //if older than preferred update day } } return false; //else return false } /******************************************************** * @purpose Return the Prof database * * @return TreeMap<String, Prof>: The treeMap containing the profs * and their rating info *********************************************************/ public ProfDatabase getProfs() { return profs; //return profs } /******************************************************** * @purpose Set the prof rating database * * @param TreeMap<String, Prof> profs: the prof rating database *********************************************************/ public void setProfs(ProfDatabase profs) { this.profs = profs; //set profs } /******************************************************** * @purpose Return a list of the courses available in the database * * @return String[]: the course titles available *********************************************************/ public String[] getCourseList(CourseType type){ Vector<String> toReturn = new Vector<String>(); for(String item: database.keySet().toArray(new String[1])){ if(database.get(item).hasSectionOfType(type)){ toReturn.add(item); } } return toReturn.toArray(new String[1]); } /******************************************************** * @purpose Return the course referenced by the key * * @return Course: the course mapped to by the key *********************************************************/ public Course getCourse(String key){ return database.get(key); //return the requested course } /******************************************************** * @purpose Return a database clone * * @return a clone of the database *********************************************************/ @Override public Database clone(){ return new Database(this); //return a clone of this database } /******************************************************** * @purpose *********************************************************/ public boolean isNewerThan(Database other){ return this.creation.compareTo(other.creation) == Compare.more.value(); } public boolean isUndergrad() { return undergrad; } public void setUndergrad(boolean undergrad) { this.undergrad = undergrad; } public boolean isGradCampus() { return gradCampus; } public void setGradCampus(boolean gradCampus) { this.gradCampus = gradCampus; } public boolean isGradDist() { return gradDist; } public void setGradDist(boolean gradDist) { this.gradDist = gradDist; } }