001package org.cpsolver.studentsct.reservation;
002
003import java.util.HashMap;
004import java.util.HashSet;
005import java.util.Map;
006import java.util.Set;
007
008import org.cpsolver.ifs.assignment.Assignment;
009import org.cpsolver.ifs.assignment.AssignmentComparable;
010import org.cpsolver.ifs.assignment.context.AbstractClassWithContext;
011import org.cpsolver.ifs.assignment.context.AssignmentConstraintContext;
012import org.cpsolver.ifs.assignment.context.CanInheritContext;
013import org.cpsolver.ifs.model.Model;
014import org.cpsolver.studentsct.StudentSectioningModel;
015import org.cpsolver.studentsct.model.Config;
016import org.cpsolver.studentsct.model.Course;
017import org.cpsolver.studentsct.model.CourseRequest;
018import org.cpsolver.studentsct.model.Enrollment;
019import org.cpsolver.studentsct.model.Offering;
020import org.cpsolver.studentsct.model.Request;
021import org.cpsolver.studentsct.model.Section;
022import org.cpsolver.studentsct.model.Student;
023import org.cpsolver.studentsct.model.Subpart;
024
025
026
027/**
028 * Abstract reservation. This abstract class allow some section, courses,
029 * and other parts to be reserved to particular group of students. A reservation
030 * can be unlimited (any number of students of that particular group can attend
031 * a course, section, etc.) or with a limit (only given number of seats is
032 * reserved to the students of the particular group).
033 * 
034 * <br>
035 * <br>
036 * 
037 * @author  Tomáš Müller
038 * @version StudentSct 1.3 (Student Sectioning)<br>
039 *          Copyright (C) 2007 - 2014 Tomáš Müller<br>
040 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
041 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
042 * <br>
043 *          This library is free software; you can redistribute it and/or modify
044 *          it under the terms of the GNU Lesser General Public License as
045 *          published by the Free Software Foundation; either version 3 of the
046 *          License, or (at your option) any later version. <br>
047 * <br>
048 *          This library is distributed in the hope that it will be useful, but
049 *          WITHOUT ANY WARRANTY; without even the implied warranty of
050 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
051 *          Lesser General Public License for more details. <br>
052 * <br>
053 *          You should have received a copy of the GNU Lesser General Public
054 *          License along with this library; if not see
055 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
056 */
057public abstract class Reservation extends AbstractClassWithContext<Request, Enrollment, Reservation.ReservationContext>
058    implements AssignmentComparable<Reservation, Request, Enrollment>, CanInheritContext<Request, Enrollment, Reservation.ReservationContext> {
059    /** Reservation unique id */
060    private long iId = 0;
061    
062    /** Is reservation expired? */
063    private boolean iExpired;
064    
065    /** Instructional offering on which the reservation is set, required */
066    private Offering iOffering;
067
068    /** One or more configurations, if applicable */ 
069    private Set<Config> iConfigs = new HashSet<Config>();
070    
071    /** One or more sections, if applicable */
072    private Map<Subpart, Set<Section>> iSections = new HashMap<Subpart, Set<Section>>();
073    
074    /** Reservation priority */
075    private int iPriority = 100;
076    
077    /** Must this reservation be used */
078    private boolean iMustBeUsed = false;
079    
080    /** Can assign over class / configuration / course limit */
081    private boolean iCanAssignOverLimit = false;
082    
083    /** Does this reservation allow for overlaps */
084    private boolean iAllowOverlap = false;
085    
086    /** Does this reservation allow for disabled sections */
087    private boolean iAllowDisabled = false;
088    
089    /** No enrollment is matching this reservation when set to true */
090    private boolean iNeverIncluded = false;
091    
092    /** Can break linked-sections constraint */
093    private boolean iBreakLinkedSections = false;
094
095    
096    /**
097     * Constructor
098     * @param id reservation unique id
099     * @param offering instructional offering on which the reservation is set
100     * @param priority reservation priority
101     * @param mustBeUsed must this reservation be used
102     * @param canAssignOverLimit can assign over class / configuration / course limit
103     * @param allowOverlap does this reservation allow for overlaps
104     */
105    public Reservation(long id, Offering offering, int priority, boolean mustBeUsed, boolean canAssignOverLimit, boolean allowOverlap) {
106        iId = id;
107        iOffering = offering;
108        iOffering.getReservations().add(this);
109        iOffering.clearReservationCache();
110        iPriority = priority;
111        iMustBeUsed = mustBeUsed;
112        iCanAssignOverLimit = canAssignOverLimit;
113        iAllowOverlap = allowOverlap;
114    }
115    
116    /**
117     * Reservation  id
118     * @return reservation unique id
119     */
120    public long getId() { return iId; }
121    
122    /**
123     * Reservation limit
124     * @return reservation limit, -1 for unlimited
125     */
126    public abstract double getReservationLimit();
127    
128    
129    /** Reservation priority (e.g., individual reservations first) 
130     * @return reservation priority
131     **/
132    public int getPriority() {
133        return iPriority;
134    }
135    
136    /**
137     * Set reservation priority (e.g., individual reservations first) 
138     * @param priority reservation priority
139     */
140    public void setPriority(int priority) {
141        iPriority = priority; 
142    }
143    
144    /**
145     * Returns true if the student is applicable for the reservation
146     * @param student a student 
147     * @param course a course
148     * @return true if student can use the reservation to get into the course / configuration / section
149     */
150    public abstract boolean isApplicable(Student student, Course course);
151    
152    @Deprecated
153    public boolean isApplicable(Student student) {
154        if (isApplicable(student, null)) return true;
155        for (Request r: student.getRequests()) {
156            if (r instanceof CourseRequest) {
157                for (Course course: ((CourseRequest) r).getCourses()) {
158                    if (course.getOffering().equals(getOffering()) && isApplicable(student, course))
159                        return true;
160                }
161            }
162        }
163        return false;
164    }
165
166    /**
167     * Instructional offering on which the reservation is set.
168     * @return instructional offering
169     */
170    public Offering getOffering() { return iOffering; }
171    
172    /**
173     * One or more configurations on which the reservation is set (optional).
174     * @return instructional offering configurations
175     */
176    public Set<Config> getConfigs() { return iConfigs; }
177    
178    /**
179     * Add a configuration (of the offering {@link Reservation#getOffering()}) to this reservation
180     * @param config instructional offering configuration
181     */
182    public void addConfig(Config config) {
183        iConfigs.add(config);
184        clearLimitCapCache();
185    }
186    
187    /**
188     * One or more sections on which the reservation is set (optional).
189     * @return class restrictions
190     */
191    public Map<Subpart, Set<Section>> getSections() { return iSections; }
192    
193    /**
194     * One or more sections on which the reservation is set (optional).
195     * @param subpart scheduling subpart
196     * @return class restrictions for the given scheduling subpart
197     */
198    public Set<Section> getSections(Subpart subpart) {
199        return iSections.get(subpart);
200    }
201    
202    /**
203     * Add a section (of the offering {@link Reservation#getOffering()}) to this reservation.
204     * This will also add all parent sections and the appropriate configuration to the offering.
205     * @param section a class restriction
206     */
207    public void addSection(Section section, boolean inclusive) {
208        if (inclusive) {
209            addConfig(section.getSubpart().getConfig());
210            while (section != null) {
211                Set<Section> sections = iSections.get(section.getSubpart());
212                if (sections == null) {
213                    sections = new HashSet<Section>();
214                    iSections.put(section.getSubpart(), sections);
215                }
216                sections.add(section);
217                section = section.getParent();
218            }
219        } else {
220            Set<Section> sections = iSections.get(section.getSubpart());
221            if (sections == null) {
222                sections = new HashSet<Section>();
223                iSections.put(section.getSubpart(), sections);
224            }
225            sections.add(section);
226        }
227        clearLimitCapCache();
228    }
229    
230    public void addSection(Section section) {
231        addSection(section, true);
232    }
233    
234    /**
235     * Return true if the given enrollment meets the reservation.
236     * @param enrollment given enrollment
237     * @return true if the given enrollment meets the reservation
238     */
239    public boolean isIncluded(Enrollment enrollment) {
240        // Never included flag is set -- return false
241        if (neverIncluded()) return false;
242        
243        // Free time request are never included
244        if (enrollment.getConfig() == null) return false;
245        
246        // Check the offering
247        if (!iOffering.equals(enrollment.getConfig().getOffering())) return false;
248        
249        if (areRestrictionsInclusive()) {
250            // If there are configurations, check the configuration
251            if (!iConfigs.isEmpty() && !iConfigs.contains(enrollment.getConfig())) return false;
252            
253            // Check all the sections of the enrollment
254            for (Section section: enrollment.getSections()) {
255                Set<Section> sections = iSections.get(section.getSubpart());
256                if (sections != null && !sections.contains(section))
257                    return false;
258            }
259            return true;
260        } else {
261            // no restrictions -> true
262            if (iConfigs.isEmpty() && iSections.isEmpty()) return true;
263            
264            // configuration match -> true
265            if (iConfigs.contains(enrollment.getConfig())) return true;
266
267            // section match -> true
268            for (Section section: enrollment.getSections()) {
269                Set<Section> sections = iSections.get(section.getSubpart());
270                if (sections != null && sections.contains(section))
271                    return true;
272            }
273            
274            // no match -> false
275            return false;
276        }
277    }
278    
279    /**
280     * True if the enrollment can be done using this reservation
281     * @param assignment current assignment
282     * @param enrollment given enrollment
283     * @return true if the given enrollment can be assigned
284     */
285    public boolean canEnroll(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
286        // Check if student can use this reservation
287        if (!isApplicable(enrollment.getStudent(), enrollment.getCourse())) return false;
288        
289        // Check if the enrollment meets the reservation
290        if (!isIncluded(enrollment)) return false;
291
292        // Check the limit
293        return getLimit(enrollment.getConfig()) < 0 || getContext(assignment).getUsedSpace() + enrollment.getRequest().getWeight() <= getLimit(enrollment.getConfig());
294    }
295    
296    /**
297     * True if can go over the course / config / section limit. Only to be used in the online sectioning. 
298     * @return can assign over class / configuration / course limit
299      */
300    public boolean canAssignOverLimit() {
301        return iCanAssignOverLimit;
302    }
303    
304    /**
305     * True if the batch solver can assign the reservation over the course / config / section limit.
306     * @return {@link Reservation#canAssignOverLimit()} and {@link StudentSectioningModel#getReservationCanAssignOverTheLimit()}
307     */
308    public boolean canBatchAssignOverLimit() {
309        return canAssignOverLimit() && (iOffering.getModel() == null || ((StudentSectioningModel)iOffering.getModel()).getReservationCanAssignOverTheLimit());
310    }
311    
312    /**
313     * Set to true if a student meeting this reservation can go over the course / config / section limit.
314     * @param canAssignOverLimit can assign over class / configuration / course limit
315     */
316    public void setCanAssignOverLimit(boolean canAssignOverLimit) {
317        iCanAssignOverLimit = canAssignOverLimit;
318    }
319    
320    /**
321     * If true, student must use the reservation (if applicable). Expired reservations do not need to be used. 
322     * @return must this reservation be used
323     */
324    public boolean mustBeUsed() {
325        return iMustBeUsed && !isExpired();
326    }
327    
328    /**
329     * If true, student must use the reservation (if applicable). Expiration date is ignored. 
330     * @return must this reservation be used
331     */
332    public boolean mustBeUsedIgnoreExpiration() {
333        return iMustBeUsed;
334    }
335    
336    /**
337     * Set to true if the student must use the reservation (if applicable)
338     * @param mustBeUsed must this reservation be used
339     */
340    public void setMustBeUsed(boolean mustBeUsed) {
341        iMustBeUsed = mustBeUsed;
342    }
343    
344    /**
345     * Reservation restrictivity (estimated percentage of enrollments that include this reservation, 1.0 reservation on the whole offering)
346     * @return computed restrictivity
347     */
348    public double getRestrictivity() {
349        if (iCachedRestrictivity == null) {
350            boolean inclusive = areRestrictionsInclusive();
351            if (getConfigs().isEmpty() && getSections().isEmpty()) return 1.0;
352            int nrChoices = 0, nrMatchingChoices = 0;
353            for (Config config: getOffering().getConfigs()) {
354                int x[] = nrChoices(config, 0, new HashSet<Section>(), getConfigs().contains(config), inclusive);
355                nrChoices += x[0];
356                nrMatchingChoices += x[1];
357            }
358            iCachedRestrictivity = ((double)nrMatchingChoices) / nrChoices;
359        }
360        return iCachedRestrictivity;
361    }
362    private Double iCachedRestrictivity = null;
363    
364    
365    /** Number of choices and number of chaing choices in the given sub enrollment */
366    private int[] nrChoices(Config config, int idx, HashSet<Section> sections, boolean matching, boolean inclusive) {
367        if (config.getSubparts().size() == idx) {
368            return new int[]{1, matching ? 1 : 0};
369        } else {
370            Subpart subpart = config.getSubparts().get(idx);
371            Set<Section> matchingSections = getSections(subpart);
372            int choicesThisSubpart = 0;
373            int matchingChoicesThisSubpart = 0;
374            for (Section section : subpart.getSections()) {
375                if (section.getParent() != null && !sections.contains(section.getParent()))
376                    continue;
377                if (section.isOverlapping(sections))
378                    continue;
379                sections.add(section);
380                boolean m = (inclusive
381                        ? matching && (matchingSections == null || matchingSections.contains(section))
382                        : matching || (matchingSections != null && matchingSections.contains(section))
383                       );
384                int[] x = nrChoices(config, 1 + idx, sections, m, inclusive);
385                choicesThisSubpart += x[0];
386                matchingChoicesThisSubpart += x[1];
387                sections.remove(section);
388            }
389            return new int[] {choicesThisSubpart, matchingChoicesThisSubpart};
390        }
391    }
392    
393    /**
394     * Priority first, than restrictivity (more restrictive first), than availability (more available first), than id 
395     */
396    @Override
397    public int compareTo(Assignment<Request, Enrollment> assignment, Reservation r) {
398        if (mustBeUsed() != r.mustBeUsed()) {
399            return (mustBeUsed() ? -1 : 1);
400        }
401        if (getPriority() != r.getPriority()) {
402            return (getPriority() < r.getPriority() ? -1 : 1);
403        }
404        int cmp = Double.compare(getRestrictivity(), r.getRestrictivity());
405        if (cmp != 0) return cmp;
406        cmp = - Double.compare(getContext(assignment).getReservedAvailableSpace(assignment, null), r.getContext(assignment).getReservedAvailableSpace(assignment, null));
407        if (cmp != 0) return cmp;
408        return Long.valueOf(getId()).compareTo(r.getId());
409    }
410    
411    /**
412     * Priority first, than restrictivity (more restrictive first), than id 
413     */
414    @Override
415    public int compareTo(Reservation r) {
416        if (mustBeUsed() != r.mustBeUsed()) {
417            return (mustBeUsed() ? -1 : 1);
418        }
419        if (getPriority() != r.getPriority()) {
420            return (getPriority() < r.getPriority() ? -1 : 1);
421        }
422        int cmp = Double.compare(getRestrictivity(), r.getRestrictivity());
423        if (cmp != 0) return cmp;
424        return Long.valueOf(getId()).compareTo(r.getId());
425    }
426    
427    /**
428     * Return minimum of two limits where -1 counts as unlimited (any limit is smaller)
429     */
430    private static double min(double l1, double l2) {
431        return (l1 < 0 ? l2 : l2 < 0 ? l1 : Math.min(l1, l2));
432    }
433    
434    /**
435     * Add two limits where -1 counts as unlimited (unlimited plus anything is unlimited)
436     */
437    private static double add(double l1, double l2) {
438        return (l1 < 0 ? -1 : l2 < 0 ? -1 : l1 + l2);
439    }
440    
441
442    /** Limit cap cache */
443    private Double iLimitCap = null;
444    private Map<Long, Double> iConfigLimitCap = null;
445
446    /**
447     * Compute limit cap (maximum number of students that can get into the offering using this reservation)
448     * @return reservation limit cap
449     */
450    public double getLimitCap() {
451        if (iLimitCap == null) iLimitCap = getLimitCapNoCache();
452        return iLimitCap;
453    }
454    
455    /**
456     * Check if restrictions are inclusive (that is for each section, the reservation also contains all its parents and the configuration)
457     */
458    public boolean areRestrictionsInclusive() {
459        for (Map.Entry<Subpart, Set<Section>> entry: getSections().entrySet()) {
460            if (getConfigs().contains(entry.getKey().getConfig())) return true;
461        }
462        return false;
463    }
464
465    /**
466     * Compute limit cap (maximum number of students that can get into the offering using this reservation)
467     */
468    private double getLimitCapNoCache() {
469        if (getConfigs().isEmpty() && getSections().isEmpty()) return -1; // no config -> can be unlimited
470        
471        if (canAssignOverLimit()) return -1; // can assign over limit -> no cap
472        
473        double cap = 0;
474        if (areRestrictionsInclusive()) {
475            // for each config
476            for (Config config: getConfigs()) {
477                // config cap
478                double configCap = config.getLimit();
479            
480                for (Map.Entry<Subpart, Set<Section>> entry: getSections().entrySet()) {
481                    if (!config.equals(entry.getKey().getConfig())) continue;
482                    Set<Section> sections = entry.getValue();
483                    
484                    // subpart cap
485                    double subpartCap = 0;
486                    for (Section section: sections)
487                        subpartCap = add(subpartCap, section.getLimit());
488            
489                    // minimize
490                    configCap = min(configCap, subpartCap);
491                }
492                
493                // add config cap
494                cap = add(cap, configCap);
495            }
496        } else {
497            // for each config
498            for (Config config: getConfigs())
499               cap = add(cap, config.getLimit());
500            // for each subpart
501            for (Map.Entry<Subpart, Set<Section>> entry: getSections().entrySet()) {
502                Set<Section> sections = entry.getValue();
503                // subpart cap
504                double subpartCap = 0;
505                for (Section section: sections)
506                    subpartCap = add(subpartCap, section.getLimit());
507                cap = add(cap, subpartCap);
508            }
509        }
510        
511        return cap;
512    }
513    
514    /**
515     * Compute limit cap (maximum number of students that can get into the offering using this reservation) for a particular configuration
516     * @return reservation limit cap
517     */
518    public double getLimitCap(Config config) {
519        Double cap = (iConfigLimitCap == null ? null : iConfigLimitCap.get(config.getId()));
520        if (cap == null) {
521            cap = getLimitCapNoCache(config);
522            if (iConfigLimitCap == null) iConfigLimitCap = new HashMap<Long, Double>();
523            iConfigLimitCap.put(config.getId(), cap);
524        }
525        return cap;
526    }
527    
528    private double getLimitCapNoCache(Config config) {
529        if (getConfigs().isEmpty() && getSections().isEmpty()) return -1; // no config -> can be unlimited
530        
531        if (canAssignOverLimit()) return -1; // can assign over limit -> no cap
532
533        if (areRestrictionsInclusive()) {
534            // no restrictions for this configuration -> no limit
535            if (!getConfigs().contains(config)) return 0;
536            
537            // config cap
538            double configCap = config.getLimit();
539        
540            for (Map.Entry<Subpart, Set<Section>> entry: getSections().entrySet()) {
541                if (!config.equals(entry.getKey().getConfig())) continue;
542                Set<Section> sections = entry.getValue();
543                
544                // subpart cap
545                double subpartCap = 0;
546                for (Section section: sections)
547                    subpartCap = add(subpartCap, section.getLimit());
548        
549                // minimize
550                configCap = min(configCap, subpartCap);
551            }
552            
553            // add config cap
554            return configCap;
555        } else {
556            double cap = 0;
557            
558            // config cap
559            if (getConfigs().contains(config))
560                cap = add(cap, config.getLimit());
561            
562            // for each subpart
563            for (Map.Entry<Subpart, Set<Section>> entry: getSections().entrySet()) {
564                if (!config.equals(entry.getKey().getConfig())) continue;
565                Set<Section> sections = entry.getValue();
566
567                // subpart cap
568                double subpartCap = 0;
569                for (Section section: sections)
570                    subpartCap = add(subpartCap, section.getLimit());
571                cap = add(cap, subpartCap);
572            }
573            
574            return cap;
575        }
576        
577    }
578    
579    /**
580     * Clear limit cap cache
581     */
582    private void clearLimitCapCache() {
583        iLimitCap = null;
584        if (iConfigLimitCap != null) iConfigLimitCap.clear();
585    }
586    
587    /**
588     * Reservation limit capped the limit cap (see {@link Reservation#getLimitCap()})
589     * @return reservation limit, -1 if unlimited
590     */
591    public double getLimit() {
592        return min(getLimitCap(), getReservationLimit());
593    }
594    
595    /**
596     * Reservation limit capped the limit cap (see {@link Reservation#getLimitCap(Config)}) for a particular configuration
597     * @param config configuration for which the limit is computed (restrictions on other configurations are ignored)
598     * @return reservation limit, -1 if unlimited
599     */
600    public double getLimit(Config config) {
601        return min(getLimitCap(config), getReservationLimit());
602    }
603    
604    /**
605     * True if holding this reservation allows a student to have attend overlapping class. 
606     * @return does this reservation allow for overlaps
607     */
608    public boolean isAllowOverlap() {
609        return iAllowOverlap;
610    }
611    
612    /**
613     * Set to true if holding this reservation allows a student to have attend overlapping class.
614     * @param allowOverlap does this reservation allow for overlaps
615     */
616    public void setAllowOverlap(boolean allowOverlap) {
617        iAllowOverlap = allowOverlap;
618    }
619    
620    /**
621     * True if holding this reservation allows a student to attend a disabled class. 
622     * @return does this reservation allow for disabled sections
623     */
624    public boolean isAllowDisabled() {
625        return iAllowDisabled;
626    }
627    
628    /**
629     * Set to true if holding this reservation allows a student to attend a disabled class
630     * @param allowDisabled does this reservation allow for disabled sections
631     */
632    public void setAllowDisabled(boolean allowDisabled) {
633        iAllowDisabled = allowDisabled;
634    }
635    
636    /**
637     * Set reservation expiration. If a reservation is expired, it works as ordinary reservation
638     * (especially the flags mutBeUsed and isAllowOverlap), except it does not block other students
639     * of getting into the offering / config / section.  
640     * @param expired is this reservation expired
641     */
642    public void setExpired(boolean expired) {
643        iExpired = expired;
644    }
645    
646    /**
647     * True if the reservation is expired. If a reservation is expired, it works as ordinary reservation
648     * (especially the flags mutBeUsed and isAllowOverlap), except it does not block other students
649     * of getting into the offering / config / section.
650     * @return is this reservation expired
651     */
652    public boolean isExpired() {
653        return iExpired;
654    }
655    
656    /**
657     * No enrollment is matching this reservation when set to true
658     */
659    public boolean neverIncluded() { return iNeverIncluded; }
660    
661    /**
662     * No enrollment is matching this reservation when set to true
663     */
664    public void setNeverIncluded(boolean neverIncluded) { iNeverIncluded = neverIncluded; }
665    
666    /**
667     * Can break linked-section constraints when set to true
668     */
669    public boolean canBreakLinkedSections() { return iBreakLinkedSections; }
670    
671    /**
672     * Can break linked-section constraints when set to true
673     */
674    public void setBreakLinkedSections(boolean breakLinkedSections) { iBreakLinkedSections = breakLinkedSections; }
675    
676    
677    @Override
678    public Model<Request, Enrollment> getModel() {
679        return getOffering().getModel();
680    }
681    
682    /**
683     * Available reserved space
684     * @param assignment current assignment
685     * @param excludeRequest excluding given request (if not null)
686     * @return available reserved space
687     **/
688    public double getReservedAvailableSpace(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
689        return getContext(assignment).getReservedAvailableSpace(assignment, excludeRequest);
690    }
691    
692    /**
693     * Available reserved space for a particular config
694     * @param assignment current assignment
695     * @param excludeRequest excluding given request (if not null)
696     * @return available reserved space
697     **/
698    public double getReservedAvailableSpace(Assignment<Request, Enrollment> assignment, Config config, Request excludeRequest) {
699        return getContext(assignment).getReservedAvailableSpace(assignment, config, excludeRequest);
700    }
701    
702    /** Enrollments assigned using this reservation 
703     * @param assignment current assignment
704     * @return assigned enrollments of this reservation
705     **/
706    public Set<Enrollment> getEnrollments(Assignment<Request, Enrollment> assignment) {
707        return getContext(assignment).getEnrollments();
708    }
709
710    @Override
711    public ReservationContext createAssignmentContext(Assignment<Request, Enrollment> assignment) {
712        return new ReservationContext(assignment);
713    }
714    
715
716    @Override
717    public ReservationContext inheritAssignmentContext(Assignment<Request, Enrollment> assignment, ReservationContext parentContext) {
718        return new ReservationContext(parentContext);
719    }
720
721    
722    public class ReservationContext implements AssignmentConstraintContext<Request, Enrollment> {
723        /** Enrollments included in this reservation */
724        private Set<Enrollment> iEnrollments = new HashSet<Enrollment>();
725        
726        /** Used part of the limit */
727        private double iUsed = 0;
728        private Map<Long, Double> iUsedByConfig = new HashMap<Long, Double>();
729        private boolean iReadOnly = false;
730
731        public ReservationContext(Assignment<Request, Enrollment> assignment) {
732            for (Course course: getOffering().getCourses())
733                for (CourseRequest request: course.getRequests()) {
734                    Enrollment enrollment = assignment.getValue(request);
735                    if (enrollment != null && Reservation.this.equals(enrollment.getReservation()))
736                        assigned(assignment, enrollment);
737                }
738        }
739        
740        public ReservationContext(ReservationContext parent) {
741            iUsed = parent.iUsed;
742            iUsedByConfig = new HashMap<Long, Double>(parent.iUsedByConfig);
743            iEnrollments = parent.iEnrollments;
744            iReadOnly = true;
745        }
746
747        /** Notify reservation about an unassignment */
748        @Override
749        public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
750            if (iReadOnly) {
751                iEnrollments = new HashSet<Enrollment>(iEnrollments);
752                iReadOnly = false;
753            }
754            if (iEnrollments.add(enrollment)) {
755                iUsed += enrollment.getRequest().getWeight();
756                Double used = iUsedByConfig.get(enrollment.getConfig().getId());
757                iUsedByConfig.put(enrollment.getConfig().getId(), enrollment.getRequest().getWeight() + (used == null ? 0.0 : used.doubleValue()));
758            }
759        }
760
761        /** Notify reservation about an assignment */
762        @Override
763        public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
764            if (iReadOnly) {
765                iEnrollments = new HashSet<Enrollment>(iEnrollments);
766                iReadOnly = false;
767            }
768            if (iEnrollments.remove(enrollment)) {
769                iUsed -= enrollment.getRequest().getWeight();
770                Double used = iUsedByConfig.get(enrollment.getConfig().getId());
771                iUsedByConfig.put(enrollment.getConfig().getId(), (used == null ? 0.0 : used.doubleValue()) - enrollment.getRequest().getWeight());
772            }
773        }
774        
775        /** Enrollments assigned using this reservation 
776         * @return assigned enrollments of this reservation
777         **/
778        public Set<Enrollment> getEnrollments() {
779            return iEnrollments;
780        }
781        
782        /** Used space 
783         * @return spaced used of this reservation
784         **/
785        public double getUsedSpace() {
786            return iUsed;
787        }
788        
789        /** Used space in a particular config
790         * @return spaced used of this reservation
791         **/
792        public double getUsedSpace(Config config) {
793            Double used = iUsedByConfig.get(config.getId());
794            return (used == null ? 0.0 : used.doubleValue());
795        }
796        
797        /**
798         * Available reserved space
799         * @param assignment current assignment
800         * @param excludeRequest excluding given request (if not null)
801         * @return available reserved space
802         **/
803        public double getReservedAvailableSpace(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
804            // Unlimited
805            if (getLimit() < 0) return Double.MAX_VALUE;
806            
807            double reserved = getLimit() - getContext(assignment).getUsedSpace();
808            if (excludeRequest != null && assignment.getValue(excludeRequest) != null && iEnrollments.contains(assignment.getValue(excludeRequest)))
809                reserved += excludeRequest.getWeight();
810            
811            return reserved;
812        }
813        
814        /**
815         * Available reserved space for a particular config
816         * @param assignment current assignment
817         * @param excludeRequest excluding given request (if not null)
818         * @return available reserved space
819         **/
820        public double getReservedAvailableSpace(Assignment<Request, Enrollment> assignment, Config config, Request excludeRequest) {
821            if (config == null) return getReservedAvailableSpace(assignment, excludeRequest);
822            
823            // Unlimited
824            if (getLimit(config) < 0) return Double.MAX_VALUE;
825            
826            double reserved = getLimit(config) - getContext(assignment).getUsedSpace(config);
827            if (excludeRequest != null && assignment.getValue(excludeRequest) != null && assignment.getValue(excludeRequest).getConfig().equals(config) && iEnrollments.contains(assignment.getValue(excludeRequest)))
828                reserved += excludeRequest.getWeight();
829            
830            return reserved;
831        }
832    }
833}