001package org.cpsolver.coursett.model;
002
003import java.util.ArrayList;
004import java.util.BitSet;
005import java.util.Collection;
006import java.util.HashSet;
007import java.util.HashMap;
008import java.util.List;
009import java.util.Locale;
010import java.util.Map;
011import java.util.Set;
012
013import org.cpsolver.coursett.Constants;
014import org.cpsolver.coursett.constraint.ClassLimitConstraint;
015import org.cpsolver.coursett.constraint.DepartmentSpreadConstraint;
016import org.cpsolver.coursett.constraint.FlexibleConstraint;
017import org.cpsolver.coursett.constraint.GroupConstraint;
018import org.cpsolver.coursett.constraint.InstructorConstraint;
019import org.cpsolver.coursett.constraint.JenrlConstraint;
020import org.cpsolver.coursett.constraint.RoomConstraint;
021import org.cpsolver.coursett.constraint.SpreadConstraint;
022import org.cpsolver.coursett.criteria.BackToBackInstructorPreferences;
023import org.cpsolver.coursett.criteria.BrokenTimePatterns;
024import org.cpsolver.coursett.criteria.DepartmentBalancingPenalty;
025import org.cpsolver.coursett.criteria.DistributionPreferences;
026import org.cpsolver.coursett.criteria.FlexibleConstraintCriterion;
027import org.cpsolver.coursett.criteria.Perturbations;
028import org.cpsolver.coursett.criteria.RoomPreferences;
029import org.cpsolver.coursett.criteria.RoomViolations;
030import org.cpsolver.coursett.criteria.SameSubpartBalancingPenalty;
031import org.cpsolver.coursett.criteria.StudentCommittedConflict;
032import org.cpsolver.coursett.criteria.StudentConflict;
033import org.cpsolver.coursett.criteria.StudentDistanceConflict;
034import org.cpsolver.coursett.criteria.StudentHardConflict;
035import org.cpsolver.coursett.criteria.StudentOverlapConflict;
036import org.cpsolver.coursett.criteria.TimePreferences;
037import org.cpsolver.coursett.criteria.TimeViolations;
038import org.cpsolver.coursett.criteria.TooBigRooms;
039import org.cpsolver.coursett.criteria.UselessHalfHours;
040import org.cpsolver.coursett.criteria.placement.DeltaTimePreference;
041import org.cpsolver.coursett.criteria.placement.HardConflicts;
042import org.cpsolver.coursett.criteria.placement.PotentialHardConflicts;
043import org.cpsolver.coursett.criteria.placement.WeightedHardConflicts;
044import org.cpsolver.ifs.assignment.Assignment;
045import org.cpsolver.ifs.constant.ConstantModel;
046import org.cpsolver.ifs.criteria.Criterion;
047import org.cpsolver.ifs.model.Constraint;
048import org.cpsolver.ifs.model.GlobalConstraint;
049import org.cpsolver.ifs.model.WeakeningConstraint;
050import org.cpsolver.ifs.solution.Solution;
051import org.cpsolver.ifs.util.DataProperties;
052import org.cpsolver.ifs.util.DistanceMetric;
053
054
055/**
056 * Timetable model.
057 * 
058 * @version CourseTT 1.3 (University Course Timetabling)<br>
059 *          Copyright (C) 2006 - 2014 Tomas Muller<br>
060 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
061 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
062 * <br>
063 *          This library is free software; you can redistribute it and/or modify
064 *          it under the terms of the GNU Lesser General Public License as
065 *          published by the Free Software Foundation; either version 3 of the
066 *          License, or (at your option) any later version. <br>
067 * <br>
068 *          This library is distributed in the hope that it will be useful, but
069 *          WITHOUT ANY WARRANTY; without even the implied warranty of
070 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
071 *          Lesser General Public License for more details. <br>
072 * <br>
073 *          You should have received a copy of the GNU Lesser General Public
074 *          License along with this library; if not see
075 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
076 */
077
078public class TimetableModel extends ConstantModel<Lecture, Placement> {
079    private static org.apache.log4j.Logger sLogger = org.apache.log4j.Logger.getLogger(TimetableModel.class);
080    private static java.text.DecimalFormat sDoubleFormat = new java.text.DecimalFormat("0.00",
081            new java.text.DecimalFormatSymbols(Locale.US));
082
083    private List<InstructorConstraint> iInstructorConstraints = new ArrayList<InstructorConstraint>();
084    private List<JenrlConstraint> iJenrlConstraints = new ArrayList<JenrlConstraint>();
085    private List<RoomConstraint> iRoomConstraints = new ArrayList<RoomConstraint>();
086    private List<DepartmentSpreadConstraint> iDepartmentSpreadConstraints = new ArrayList<DepartmentSpreadConstraint>();
087    private List<SpreadConstraint> iSpreadConstraints = new ArrayList<SpreadConstraint>();
088    private List<GroupConstraint> iGroupConstraints = new ArrayList<GroupConstraint>();
089    private List<ClassLimitConstraint> iClassLimitConstraints = new ArrayList<ClassLimitConstraint>();
090    private List<FlexibleConstraint> iFlexibleConstraints = new ArrayList<FlexibleConstraint>();
091    private DataProperties iProperties = null;
092    private int iYear = -1;
093    private List<BitSet> iWeeks = null;
094
095    private HashSet<Student> iAllStudents = new HashSet<Student>();
096    
097    private DistanceMetric iDistanceMetric = null;
098    
099    private StudentSectioning iStudentSectioning = null;
100
101    public TimetableModel(DataProperties properties) {
102        super();
103        iProperties = properties;
104        iDistanceMetric = new DistanceMetric(properties);
105        if (properties.getPropertyBoolean("OnFlySectioning.Enabled", false))
106            addModelListener(new OnFlySectioning(this));
107        String criteria = properties.getProperty("General.Criteria",
108                // Objectives
109                StudentConflict.class.getName() + ";" +
110                StudentDistanceConflict.class.getName() + ";" +
111                StudentHardConflict.class.getName() + ";" +
112                StudentCommittedConflict.class.getName() + ";" +
113                StudentOverlapConflict.class.getName() + ";" +
114                UselessHalfHours.class.getName() + ";" +
115                BrokenTimePatterns.class.getName() + ";" +
116                TooBigRooms.class.getName() + ";" +
117                TimePreferences.class.getName() + ";" +
118                RoomPreferences.class.getName() + ";" +
119                DistributionPreferences.class.getName() + ";" +
120                SameSubpartBalancingPenalty.class.getName() + ";" +
121                DepartmentBalancingPenalty.class.getName() + ";" +
122                BackToBackInstructorPreferences.class.getName() + ";" +
123                Perturbations.class.getName() + ";" +
124                // Additional placement selection criteria
125                // AssignmentCount.class.getName() + ";" +
126                DeltaTimePreference.class.getName() + ";" +
127                HardConflicts.class.getName() + ";" +
128                PotentialHardConflicts.class.getName() + ";" +
129                FlexibleConstraintCriterion.class.getName() + ";" +
130                WeightedHardConflicts.class.getName());                
131        // Interactive mode -- count time / room violations
132        if (properties.getPropertyBoolean("General.InteractiveMode", false))
133            criteria += ";" + TimeViolations.class.getName() + ";" + RoomViolations.class.getName();
134        // Additional (custom) criteria
135        criteria += ";" + properties.getProperty("General.AdditionalCriteria", "");
136        for (String criterion: criteria.split("\\;")) {
137            if (criterion == null || criterion.isEmpty()) continue;
138            try {
139                @SuppressWarnings("unchecked")
140                Class<Criterion<Lecture, Placement>> clazz = (Class<Criterion<Lecture, Placement>>)Class.forName(criterion);
141                addCriterion(clazz.newInstance());
142            } catch (Exception e) {
143                sLogger.error("Unable to use " + criterion + ": " + e.getMessage());
144            }
145        }
146        try {
147            String studentSectioningClassName = properties.getProperty("StudentSectioning.Class", DefaultStudentSectioning.class.getName());
148            Class<?> studentSectioningClass = Class.forName(studentSectioningClassName);
149            iStudentSectioning = (StudentSectioning)studentSectioningClass.getConstructor(TimetableModel.class).newInstance(this);
150        } catch (Exception e) {
151            sLogger.error("Failed to load custom student sectioning class: " + e.getMessage());
152            iStudentSectioning = new DefaultStudentSectioning(this);
153        }
154    }
155
156    public DistanceMetric getDistanceMetric() {
157        return iDistanceMetric;
158    }
159    
160    /**
161     * Returns interface to the student sectioning functions needed during course timetabling.
162     * Defaults to an instance of {@link DefaultStudentSectioning}, can be changed using the StudentSectioning.Class parameter.
163     * @return student sectioning
164     */
165    public StudentSectioning getStudentSectioning() {
166        return iStudentSectioning;
167    }
168
169    public DataProperties getProperties() {
170        return iProperties;
171    }
172
173    /**
174     * Student final sectioning (switching students between sections of the same
175     * class in order to minimize overall number of student conflicts)
176     * @param assignment current assignment
177     */
178    public void switchStudents(Assignment<Lecture, Placement> assignment) {
179        getStudentSectioning().switchStudents(new Solution<Lecture, Placement>(this, assignment));
180    }
181
182    public Map<String, String> getBounds(Assignment<Lecture, Placement> assignment) {
183        Map<String, String> ret = new HashMap<String, String>();
184        ret.put("Room preferences min", "" + getCriterion(RoomPreferences.class).getBounds(assignment)[0]);
185        ret.put("Room preferences max", "" + getCriterion(RoomPreferences.class).getBounds(assignment)[1]);
186        ret.put("Time preferences min", "" + getCriterion(TimePreferences.class).getBounds(assignment)[0]);
187        ret.put("Time preferences max", "" + getCriterion(TimePreferences.class).getBounds(assignment)[1]);
188        ret.put("Distribution preferences min", "" + getCriterion(DistributionPreferences.class).getBounds(assignment)[0]);
189        ret.put("Distribution preferences max", "" + getCriterion(DistributionPreferences.class).getBounds(assignment)[1]);
190        if (getProperties().getPropertyBoolean("General.UseDistanceConstraints", false)) {
191            ret.put("Back-to-back instructor preferences max", "" + getCriterion(BackToBackInstructorPreferences.class).getBounds(assignment)[1]);
192        }
193        ret.put("Too big rooms max", "" + getCriterion(TooBigRooms.class).getBounds(assignment)[0]);
194        ret.put("Useless half-hours", "" + getCriterion(UselessHalfHours.class).getBounds(assignment)[0]);
195        return ret;
196    }
197
198    /** Global info */
199    @Override
200    public Map<String, String> getInfo(Assignment<Lecture, Placement> assignment) {
201        Map<String, String> ret = super.getInfo(assignment);
202        ret.put("Memory usage", getMem());
203        
204        Criterion<Lecture, Placement> rp = getCriterion(RoomPreferences.class);
205        Criterion<Lecture, Placement> rv = getCriterion(RoomViolations.class);
206        ret.put("Room preferences", getPerc(rp.getValue(assignment), rp.getBounds(assignment)[0], rp.getBounds(assignment)[1]) + "% (" + Math.round(rp.getValue(assignment)) + ")"
207                + (rv != null && rv.getValue(assignment) >= 0.5 ? " [hard:" + Math.round(rv.getValue(assignment)) + "]" : ""));
208        
209        Criterion<Lecture, Placement> tp = getCriterion(TimePreferences.class);
210        Criterion<Lecture, Placement> tv = getCriterion(TimeViolations.class);
211        ret.put("Time preferences", getPerc(tp.getValue(assignment), tp.getBounds(assignment)[0], tp.getBounds(assignment)[1]) + "% (" + sDoubleFormat.format(tp.getValue(assignment)) + ")"
212                + (tv != null && tv.getValue(assignment) >= 0.5 ? " [hard:" + Math.round(tv.getValue(assignment)) + "]" : ""));
213
214        Criterion<Lecture, Placement> dp = getCriterion(DistributionPreferences.class);
215        ret.put("Distribution preferences", getPerc(dp.getValue(assignment), dp.getBounds(assignment)[0], dp.getBounds(assignment)[1]) + "% (" + sDoubleFormat.format(dp.getValue(assignment)) + ")");
216        
217        Criterion<Lecture, Placement> sc = getCriterion(StudentConflict.class);
218        Criterion<Lecture, Placement> shc = getCriterion(StudentHardConflict.class);
219        Criterion<Lecture, Placement> sdc = getCriterion(StudentDistanceConflict.class);
220        Criterion<Lecture, Placement> scc = getCriterion(StudentCommittedConflict.class);
221        ret.put("Student conflicts", Math.round(scc.getValue(assignment) + sc.getValue(assignment)) +
222                " [committed:" + Math.round(scc.getValue(assignment)) +
223                ", distance:" + Math.round(sdc.getValue(assignment)) +
224                ", hard:" + Math.round(shc.getValue(assignment)) + "]");
225        
226        if (!getSpreadConstraints().isEmpty()) {
227            Criterion<Lecture, Placement> ip = getCriterion(BackToBackInstructorPreferences.class);
228            ret.put("Back-to-back instructor preferences", getPerc(ip.getValue(assignment), ip.getBounds(assignment)[0], ip.getBounds(assignment)[1]) + "% (" + Math.round(ip.getValue(assignment)) + ")");
229        }
230
231        if (!getDepartmentSpreadConstraints().isEmpty()) {
232            Criterion<Lecture, Placement> dbp = getCriterion(DepartmentBalancingPenalty.class);
233            ret.put("Department balancing penalty", sDoubleFormat.format(dbp.getValue(assignment)));
234        }
235        
236        Criterion<Lecture, Placement> sbp = getCriterion(SameSubpartBalancingPenalty.class);
237        ret.put("Same subpart balancing penalty", sDoubleFormat.format(sbp.getValue(assignment)));
238        
239        Criterion<Lecture, Placement> tbr = getCriterion(TooBigRooms.class);
240        ret.put("Too big rooms", getPercRev(tbr.getValue(assignment), tbr.getBounds(assignment)[1], tbr.getBounds(assignment)[0]) + "% (" + Math.round(tbr.getValue(assignment)) + ")");
241        
242        Criterion<Lecture, Placement> uh = getCriterion(UselessHalfHours.class);
243        Criterion<Lecture, Placement> bt = getCriterion(BrokenTimePatterns.class);
244
245        ret.put("Useless half-hours", getPercRev(uh.getValue(assignment) + bt.getValue(assignment), 0, Constants.sPreferenceLevelStronglyDiscouraged * bt.getBounds(assignment)[0]) +
246                "% (" + Math.round(uh.getValue(assignment)) + " + " + Math.round(bt.getValue(assignment)) + ")");
247        return ret;
248    }
249
250    @Override
251    public Map<String, String> getInfo(Assignment<Lecture, Placement> assignment, Collection<Lecture> variables) {
252        Map<String, String> ret = super.getInfo(assignment, variables);
253        
254        ret.put("Memory usage", getMem());
255        
256        Criterion<Lecture, Placement> rp = getCriterion(RoomPreferences.class);
257        ret.put("Room preferences", getPerc(rp.getValue(assignment, variables), rp.getBounds(assignment, variables)[0], rp.getBounds(assignment, variables)[1]) + "% (" + Math.round(rp.getValue(assignment, variables)) + ")");
258        
259        Criterion<Lecture, Placement> tp = getCriterion(TimePreferences.class);
260        ret.put("Time preferences", getPerc(tp.getValue(assignment, variables), tp.getBounds(assignment, variables)[0], tp.getBounds(assignment, variables)[1]) + "% (" + sDoubleFormat.format(tp.getValue(assignment, variables)) + ")"); 
261
262        Criterion<Lecture, Placement> dp = getCriterion(DistributionPreferences.class);
263        ret.put("Distribution preferences", getPerc(dp.getValue(assignment, variables), dp.getBounds(assignment, variables)[0], dp.getBounds(assignment, variables)[1]) + "% (" + sDoubleFormat.format(dp.getValue(assignment, variables)) + ")");
264        
265        Criterion<Lecture, Placement> sc = getCriterion(StudentConflict.class);
266        Criterion<Lecture, Placement> shc = getCriterion(StudentHardConflict.class);
267        Criterion<Lecture, Placement> sdc = getCriterion(StudentDistanceConflict.class);
268        Criterion<Lecture, Placement> scc = getCriterion(StudentCommittedConflict.class);
269        ret.put("Student conflicts", Math.round(scc.getValue(assignment, variables) + sc.getValue(assignment, variables)) +
270                " [committed:" + Math.round(scc.getValue(assignment, variables)) +
271                ", distance:" + Math.round(sdc.getValue(assignment, variables)) +
272                ", hard:" + Math.round(shc.getValue(assignment, variables)) + "]");
273        
274        if (!getSpreadConstraints().isEmpty()) {
275            Criterion<Lecture, Placement> ip = getCriterion(BackToBackInstructorPreferences.class);
276            ret.put("Back-to-back instructor preferences", getPerc(ip.getValue(assignment, variables), ip.getBounds(assignment, variables)[0], ip.getBounds(assignment, variables)[1]) + "% (" + Math.round(ip.getValue(assignment, variables)) + ")");
277        }
278
279        if (!getDepartmentSpreadConstraints().isEmpty()) {
280            Criterion<Lecture, Placement> dbp = getCriterion(DepartmentBalancingPenalty.class);
281            ret.put("Department balancing penalty", sDoubleFormat.format(dbp.getValue(assignment, variables)));
282        }
283        
284        Criterion<Lecture, Placement> sbp = getCriterion(SameSubpartBalancingPenalty.class);
285        ret.put("Same subpart balancing penalty", sDoubleFormat.format(sbp.getValue(assignment, variables)));
286        
287        Criterion<Lecture, Placement> tbr = getCriterion(TooBigRooms.class);
288        ret.put("Too big rooms", getPercRev(tbr.getValue(assignment, variables), tbr.getBounds(assignment, variables)[1], tbr.getBounds(assignment, variables)[0]) + "% (" + Math.round(tbr.getValue(assignment, variables)) + ")");
289        
290        Criterion<Lecture, Placement> uh = getCriterion(UselessHalfHours.class);
291        Criterion<Lecture, Placement> bt = getCriterion(BrokenTimePatterns.class);
292
293        ret.put("Useless half-hours", getPercRev(uh.getValue(assignment, variables) + bt.getValue(assignment, variables), 0, Constants.sPreferenceLevelStronglyDiscouraged * bt.getBounds(assignment, variables)[0]) +
294                "% (" + Math.round(uh.getValue(assignment, variables)) + " + " + Math.round(bt.getValue(assignment, variables)) + ")");
295        return ret;
296    }
297
298    @Override
299    public void addConstraint(Constraint<Lecture, Placement> constraint) {
300        super.addConstraint(constraint);
301        if (constraint instanceof InstructorConstraint) {
302            iInstructorConstraints.add((InstructorConstraint) constraint);
303        } else if (constraint instanceof JenrlConstraint) {
304            iJenrlConstraints.add((JenrlConstraint) constraint);
305        } else if (constraint instanceof RoomConstraint) {
306            iRoomConstraints.add((RoomConstraint) constraint);
307        } else if (constraint instanceof DepartmentSpreadConstraint) {
308            iDepartmentSpreadConstraints.add((DepartmentSpreadConstraint) constraint);
309        } else if (constraint instanceof SpreadConstraint) {
310            iSpreadConstraints.add((SpreadConstraint) constraint);
311        } else if (constraint instanceof ClassLimitConstraint) {
312            iClassLimitConstraints.add((ClassLimitConstraint) constraint);
313        } else if (constraint instanceof GroupConstraint) {
314            iGroupConstraints.add((GroupConstraint) constraint);
315        } else if (constraint instanceof FlexibleConstraint) {
316            iFlexibleConstraints.add((FlexibleConstraint) constraint);
317        }
318    }
319
320    @Override
321    public void removeConstraint(Constraint<Lecture, Placement> constraint) {
322        super.removeConstraint(constraint);
323        if (constraint instanceof InstructorConstraint) {
324            iInstructorConstraints.remove(constraint);
325        } else if (constraint instanceof JenrlConstraint) {
326            iJenrlConstraints.remove(constraint);
327        } else if (constraint instanceof RoomConstraint) {
328            iRoomConstraints.remove(constraint);
329        } else if (constraint instanceof DepartmentSpreadConstraint) {
330            iDepartmentSpreadConstraints.remove(constraint);
331        } else if (constraint instanceof SpreadConstraint) {
332            iSpreadConstraints.remove(constraint);
333        } else if (constraint instanceof ClassLimitConstraint) {
334            iClassLimitConstraints.remove(constraint);
335        } else if (constraint instanceof GroupConstraint) {
336            iGroupConstraints.remove(constraint);
337        } else if (constraint instanceof FlexibleConstraint) {
338            iFlexibleConstraints.remove(constraint);
339        }
340    }
341
342    /** The list of all instructor constraints 
343     * @return list of instructor constraints
344     **/
345    public List<InstructorConstraint> getInstructorConstraints() {
346        return iInstructorConstraints;
347    }
348
349    /** The list of all group constraints
350     * @return list of group (distribution) constraints
351     **/
352    public List<GroupConstraint> getGroupConstraints() {
353        return iGroupConstraints;
354    }
355
356    /** The list of all jenrl constraints
357     * @return list of join enrollment constraints
358     **/
359    public List<JenrlConstraint> getJenrlConstraints() {
360        return iJenrlConstraints;
361    }
362
363    /** The list of all room constraints 
364     * @return list of room constraints
365     **/
366    public List<RoomConstraint> getRoomConstraints() {
367        return iRoomConstraints;
368    }
369
370    /** The list of all departmental spread constraints 
371     * @return list of department spread constraints
372     **/
373    public List<DepartmentSpreadConstraint> getDepartmentSpreadConstraints() {
374        return iDepartmentSpreadConstraints;
375    }
376
377    public List<SpreadConstraint> getSpreadConstraints() {
378        return iSpreadConstraints;
379    }
380
381    public List<ClassLimitConstraint> getClassLimitConstraints() {
382        return iClassLimitConstraints;
383    }
384    
385    public List<FlexibleConstraint> getFlexibleConstraints() {
386        return iFlexibleConstraints;
387    }
388    
389    @Override
390    public double getTotalValue(Assignment<Lecture, Placement> assignment) {
391        double ret = 0;
392        for (Criterion<Lecture, Placement> criterion: getCriteria())
393            ret += criterion.getWeightedValue(assignment);
394        return ret;
395    }
396
397    @Override
398    public double getTotalValue(Assignment<Lecture, Placement> assignment, Collection<Lecture> variables) {
399        double ret = 0;
400        for (Criterion<Lecture, Placement> criterion: getCriteria())
401            ret += criterion.getWeightedValue(assignment, variables);
402        return ret;
403    }
404
405    public int getYear() {
406        return iYear;
407    }
408
409    public void setYear(int year) {
410        iYear = year;
411    }
412
413    public Set<Student> getAllStudents() {
414        return iAllStudents;
415    }
416
417    public void addStudent(Student student) {
418        iAllStudents.add(student);
419    }
420
421    public void removeStudent(Student student) {
422        iAllStudents.remove(student);
423    }
424
425    /**
426     * Returns amount of allocated memory.
427     * 
428     * @return amount of allocated memory to be written in the log
429     */
430    public static synchronized String getMem() {
431        Runtime rt = Runtime.getRuntime();
432        return sDoubleFormat.format(((double) (rt.totalMemory() - rt.freeMemory())) / 1048576) + "M";
433    }
434    
435    
436    /**
437     * Returns the set of conflicting variables with this value, if it is
438     * assigned to its variable. Conflicts with constraints that implement
439     * {@link WeakeningConstraint} are ignored.
440     * @param assignment current assignment
441     * @param value placement that is being considered
442     * @return computed conflicting assignments
443     */
444    public Set<Placement> conflictValuesSkipWeakeningConstraints(Assignment<Lecture, Placement> assignment, Placement value) {
445        Set<Placement> conflictValues = new HashSet<Placement>();
446        for (Constraint<Lecture, Placement> constraint : value.variable().hardConstraints()) {
447            if (constraint instanceof WeakeningConstraint) continue;
448            if (constraint instanceof GroupConstraint)
449                ((GroupConstraint)constraint).computeConflictsNoForwardCheck(assignment, value, conflictValues);
450            else
451                constraint.computeConflicts(assignment, value, conflictValues);
452        }
453        for (GlobalConstraint<Lecture, Placement> constraint : globalConstraints()) {
454            if (constraint instanceof WeakeningConstraint) continue;
455            constraint.computeConflicts(assignment, value, conflictValues);
456        }
457        return conflictValues;
458    }
459    
460    /**
461     * The method creates date patterns (bitsets) which represent the weeks of a
462     * semester.
463     *      
464     * @return a list of BitSets which represents the weeks of a semester.
465     */
466    public List<BitSet> getWeeks() {
467        if (iWeeks == null) {
468            String defaultDatePattern = getProperties().getProperty("DatePattern.CustomDatePattern", null);
469            if (defaultDatePattern == null){                
470                defaultDatePattern = getProperties().getProperty("DatePattern.Default");
471            }
472            BitSet fullTerm = null;
473            if (defaultDatePattern == null) {
474                // Take the date pattern that is being used most often
475                Map<Long, Integer> counter = new HashMap<Long, Integer>();
476                int max = 0; String name = null; Long id = null;
477                for (Lecture lecture: variables()) {
478                    if (lecture.isCommitted()) continue;
479                    for (TimeLocation time: lecture.timeLocations()) {
480                        if (time.getWeekCode() != null && time.getDatePatternId() != null) {
481                            int count = 1;
482                            if (counter.containsKey(time.getDatePatternId()))
483                                count += counter.get(time.getDatePatternId());
484                            counter.put(time.getDatePatternId(), count);
485                            if (count > max) {
486                                max = count; fullTerm = time.getWeekCode(); name = time.getDatePatternName(); id = time.getDatePatternId();
487                            }
488                        }
489                    }
490                }
491                sLogger.info("Using date pattern " + name + " (id " + id + ") as the default.");
492            } else {
493                // Create default date pattern
494                fullTerm = new BitSet(defaultDatePattern.length());
495                for (int i = 0; i < defaultDatePattern.length(); i++) {
496                    if (defaultDatePattern.charAt(i) == 49) {
497                        fullTerm.set(i);
498                    }
499                }
500            }
501            
502            if (fullTerm == null) return null;
503            
504            iWeeks = new ArrayList<BitSet>();
505            if (getProperties().getPropertyBoolean("DatePattern.ShiftWeeks", false)) {
506                // Cut date pattern into weeks (each week takes 7 consecutive bits, starting on the next positive bit)
507                for (int i = fullTerm.nextSetBit(0); i < fullTerm.length(); ) {
508                    if (!fullTerm.get(i)) {
509                        i++; continue;
510                    }
511                    BitSet w = new BitSet(i + 7);
512                    for (int j = 0; j < 7; j++)
513                        if (fullTerm.get(i + j)) w.set(i + j);
514                    iWeeks.add(w);
515                    i += 7;
516                }                
517            } else {
518                // Cut date pattern into weeks (each week takes 7 consecutive bits starting on the first bit of the default date pattern, no pauses between weeks)
519                for (int i = fullTerm.nextSetBit(0); i < fullTerm.length(); ) {
520                    BitSet w = new BitSet(i + 7);
521                    for (int j = 0; j < 7; j++)
522                        if (fullTerm.get(i + j)) w.set(i + j);
523                    iWeeks.add(w);
524                    i += 7;
525                }
526            }
527        }
528        return iWeeks;
529    }
530}