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     * @return true if student can use the reservation to get into the course / configuration / section
148     */
149    public abstract boolean isApplicable(Student student);
150
151    /**
152     * Instructional offering on which the reservation is set.
153     * @return instructional offering
154     */
155    public Offering getOffering() { return iOffering; }
156    
157    /**
158     * One or more configurations on which the reservation is set (optional).
159     * @return instructional offering configurations
160     */
161    public Set<Config> getConfigs() { return iConfigs; }
162    
163    /**
164     * Add a configuration (of the offering {@link Reservation#getOffering()}) to this reservation
165     * @param config instructional offering configuration
166     */
167    public void addConfig(Config config) {
168        iConfigs.add(config);
169        clearLimitCapCache();
170    }
171    
172    /**
173     * One or more sections on which the reservation is set (optional).
174     * @return class restrictions
175     */
176    public Map<Subpart, Set<Section>> getSections() { return iSections; }
177    
178    /**
179     * One or more sections on which the reservation is set (optional).
180     * @param subpart scheduling subpart
181     * @return class restrictions for the given scheduling subpart
182     */
183    public Set<Section> getSections(Subpart subpart) {
184        return iSections.get(subpart);
185    }
186    
187    /**
188     * Add a section (of the offering {@link Reservation#getOffering()}) to this reservation.
189     * This will also add all parent sections and the appropriate configuration to the offering.
190     * @param section a class restriction
191     */
192    public void addSection(Section section, boolean inclusive) {
193        if (inclusive) {
194            addConfig(section.getSubpart().getConfig());
195            while (section != null) {
196                Set<Section> sections = iSections.get(section.getSubpart());
197                if (sections == null) {
198                    sections = new HashSet<Section>();
199                    iSections.put(section.getSubpart(), sections);
200                }
201                sections.add(section);
202                section = section.getParent();
203            }
204        } else {
205            Set<Section> sections = iSections.get(section.getSubpart());
206            if (sections == null) {
207                sections = new HashSet<Section>();
208                iSections.put(section.getSubpart(), sections);
209            }
210            sections.add(section);
211        }
212        clearLimitCapCache();
213    }
214    
215    public void addSection(Section section) {
216        addSection(section, true);
217    }
218    
219    /**
220     * Return true if the given enrollment meets the reservation.
221     * @param enrollment given enrollment
222     * @return true if the given enrollment meets the reservation
223     */
224    public boolean isIncluded(Enrollment enrollment) {
225        // Never included flag is set -- return false
226        if (neverIncluded()) return false;
227        
228        // Free time request are never included
229        if (enrollment.getConfig() == null) return false;
230        
231        // Check the offering
232        if (!iOffering.equals(enrollment.getConfig().getOffering())) return false;
233        
234        if (areRestrictionsInclusive()) {
235            // If there are configurations, check the configuration
236            if (!iConfigs.isEmpty() && !iConfigs.contains(enrollment.getConfig())) return false;
237            
238            // Check all the sections of the enrollment
239            for (Section section: enrollment.getSections()) {
240                Set<Section> sections = iSections.get(section.getSubpart());
241                if (sections != null && !sections.contains(section))
242                    return false;
243            }
244            return true;
245        } else {
246            // no restrictions -> true
247            if (iConfigs.isEmpty() && iSections.isEmpty()) return true;
248            
249            // configuration match -> true
250            if (iConfigs.contains(enrollment.getConfig())) return true;
251
252            // section match -> true
253            for (Section section: enrollment.getSections()) {
254                Set<Section> sections = iSections.get(section.getSubpart());
255                if (sections != null && sections.contains(section))
256                    return true;
257            }
258            
259            // no match -> false
260            return false;
261        }
262    }
263    
264    /**
265     * True if the enrollment can be done using this reservation
266     * @param assignment current assignment
267     * @param enrollment given enrollment
268     * @return true if the given enrollment can be assigned
269     */
270    public boolean canEnroll(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
271        // Check if student can use this reservation
272        if (!isApplicable(enrollment.getStudent())) return false;
273        
274        // Check if the enrollment meets the reservation
275        if (!isIncluded(enrollment)) return false;
276
277        // Check the limit
278        return getLimit(enrollment.getConfig()) < 0 || getContext(assignment).getUsedSpace() + enrollment.getRequest().getWeight() <= getLimit(enrollment.getConfig());
279    }
280    
281    /**
282     * True if can go over the course / config / section limit. Only to be used in the online sectioning. 
283     * @return can assign over class / configuration / course limit
284      */
285    public boolean canAssignOverLimit() {
286        return iCanAssignOverLimit;
287    }
288    
289    /**
290     * True if the batch solver can assign the reservation over the course / config / section limit.
291     * @return {@link Reservation#canAssignOverLimit()} and {@link StudentSectioningModel#getReservationCanAssignOverTheLimit()}
292     */
293    public boolean canBatchAssignOverLimit() {
294        return canAssignOverLimit() && (iOffering.getModel() == null || ((StudentSectioningModel)iOffering.getModel()).getReservationCanAssignOverTheLimit());
295    }
296    
297    /**
298     * Set to true if a student meeting this reservation can go over the course / config / section limit.
299     * @param canAssignOverLimit can assign over class / configuration / course limit
300     */
301    public void setCanAssignOverLimit(boolean canAssignOverLimit) {
302        iCanAssignOverLimit = canAssignOverLimit;
303    }
304    
305    /**
306     * If true, student must use the reservation (if applicable). Expired reservations do not need to be used. 
307     * @return must this reservation be used
308     */
309    public boolean mustBeUsed() {
310        return iMustBeUsed && !isExpired();
311    }
312    
313    /**
314     * If true, student must use the reservation (if applicable). Expiration date is ignored. 
315     * @return must this reservation be used
316     */
317    public boolean mustBeUsedIgnoreExpiration() {
318        return iMustBeUsed;
319    }
320    
321    /**
322     * Set to true if the student must use the reservation (if applicable)
323     * @param mustBeUsed must this reservation be used
324     */
325    public void setMustBeUsed(boolean mustBeUsed) {
326        iMustBeUsed = mustBeUsed;
327    }
328    
329    /**
330     * Reservation restrictivity (estimated percentage of enrollments that include this reservation, 1.0 reservation on the whole offering)
331     * @return computed restrictivity
332     */
333    public double getRestrictivity() {
334        if (iCachedRestrictivity == null) {
335            boolean inclusive = areRestrictionsInclusive();
336            if (getConfigs().isEmpty() && getSections().isEmpty()) return 1.0;
337            int nrChoices = 0, nrMatchingChoices = 0;
338            for (Config config: getOffering().getConfigs()) {
339                int x[] = nrChoices(config, 0, new HashSet<Section>(), getConfigs().contains(config), inclusive);
340                nrChoices += x[0];
341                nrMatchingChoices += x[1];
342            }
343            iCachedRestrictivity = ((double)nrMatchingChoices) / nrChoices;
344        }
345        return iCachedRestrictivity;
346    }
347    private Double iCachedRestrictivity = null;
348    
349    
350    /** Number of choices and number of chaing choices in the given sub enrollment */
351    private int[] nrChoices(Config config, int idx, HashSet<Section> sections, boolean matching, boolean inclusive) {
352        if (config.getSubparts().size() == idx) {
353            return new int[]{1, matching ? 1 : 0};
354        } else {
355            Subpart subpart = config.getSubparts().get(idx);
356            Set<Section> matchingSections = getSections(subpart);
357            int choicesThisSubpart = 0;
358            int matchingChoicesThisSubpart = 0;
359            for (Section section : subpart.getSections()) {
360                if (section.getParent() != null && !sections.contains(section.getParent()))
361                    continue;
362                if (section.isOverlapping(sections))
363                    continue;
364                sections.add(section);
365                boolean m = (inclusive
366                        ? matching && (matchingSections == null || matchingSections.contains(section))
367                        : matching || (matchingSections != null && matchingSections.contains(section))
368                       );
369                int[] x = nrChoices(config, 1 + idx, sections, m, inclusive);
370                choicesThisSubpart += x[0];
371                matchingChoicesThisSubpart += x[1];
372                sections.remove(section);
373            }
374            return new int[] {choicesThisSubpart, matchingChoicesThisSubpart};
375        }
376    }
377    
378    /**
379     * Priority first, than restrictivity (more restrictive first), than availability (more available first), than id 
380     */
381    @Override
382    public int compareTo(Assignment<Request, Enrollment> assignment, Reservation r) {
383        if (mustBeUsed() != r.mustBeUsed()) {
384            return (mustBeUsed() ? -1 : 1);
385        }
386        if (getPriority() != r.getPriority()) {
387            return (getPriority() < r.getPriority() ? -1 : 1);
388        }
389        int cmp = Double.compare(getRestrictivity(), r.getRestrictivity());
390        if (cmp != 0) return cmp;
391        cmp = - Double.compare(getContext(assignment).getReservedAvailableSpace(assignment, null), r.getContext(assignment).getReservedAvailableSpace(assignment, null));
392        if (cmp != 0) return cmp;
393        return Long.valueOf(getId()).compareTo(r.getId());
394    }
395    
396    /**
397     * Priority first, than restrictivity (more restrictive first), than id 
398     */
399    @Override
400    public int compareTo(Reservation r) {
401        if (mustBeUsed() != r.mustBeUsed()) {
402            return (mustBeUsed() ? -1 : 1);
403        }
404        if (getPriority() != r.getPriority()) {
405            return (getPriority() < r.getPriority() ? -1 : 1);
406        }
407        int cmp = Double.compare(getRestrictivity(), r.getRestrictivity());
408        if (cmp != 0) return cmp;
409        return Long.valueOf(getId()).compareTo(r.getId());
410    }
411    
412    /**
413     * Return minimum of two limits where -1 counts as unlimited (any limit is smaller)
414     */
415    private static double min(double l1, double l2) {
416        return (l1 < 0 ? l2 : l2 < 0 ? l1 : Math.min(l1, l2));
417    }
418    
419    /**
420     * Add two limits where -1 counts as unlimited (unlimited plus anything is unlimited)
421     */
422    private static double add(double l1, double l2) {
423        return (l1 < 0 ? -1 : l2 < 0 ? -1 : l1 + l2);
424    }
425    
426
427    /** Limit cap cache */
428    private Double iLimitCap = null;
429    private Map<Long, Double> iConfigLimitCap = null;
430
431    /**
432     * Compute limit cap (maximum number of students that can get into the offering using this reservation)
433     * @return reservation limit cap
434     */
435    public double getLimitCap() {
436        if (iLimitCap == null) iLimitCap = getLimitCapNoCache();
437        return iLimitCap;
438    }
439    
440    /**
441     * Check if restrictions are inclusive (that is for each section, the reservation also contains all its parents and the configuration)
442     */
443    public boolean areRestrictionsInclusive() {
444        for (Map.Entry<Subpart, Set<Section>> entry: getSections().entrySet()) {
445            if (getConfigs().contains(entry.getKey().getConfig())) return true;
446        }
447        return false;
448    }
449
450    /**
451     * Compute limit cap (maximum number of students that can get into the offering using this reservation)
452     */
453    private double getLimitCapNoCache() {
454        if (getConfigs().isEmpty() && getSections().isEmpty()) return -1; // no config -> can be unlimited
455        
456        if (canAssignOverLimit()) return -1; // can assign over limit -> no cap
457        
458        double cap = 0;
459        if (areRestrictionsInclusive()) {
460            // for each config
461            for (Config config: getConfigs()) {
462                // config cap
463                double configCap = config.getLimit();
464            
465                for (Map.Entry<Subpart, Set<Section>> entry: getSections().entrySet()) {
466                    if (!config.equals(entry.getKey().getConfig())) continue;
467                    Set<Section> sections = entry.getValue();
468                    
469                    // subpart cap
470                    double subpartCap = 0;
471                    for (Section section: sections)
472                        subpartCap = add(subpartCap, section.getLimit());
473            
474                    // minimize
475                    configCap = min(configCap, subpartCap);
476                }
477                
478                // add config cap
479                cap = add(cap, configCap);
480            }
481        } else {
482            // for each config
483            for (Config config: getConfigs())
484               cap = add(cap, config.getLimit());
485            // for each subpart
486            for (Map.Entry<Subpart, Set<Section>> entry: getSections().entrySet()) {
487                Set<Section> sections = entry.getValue();
488                // subpart cap
489                double subpartCap = 0;
490                for (Section section: sections)
491                    subpartCap = add(subpartCap, section.getLimit());
492                cap = add(cap, subpartCap);
493            }
494        }
495        
496        return cap;
497    }
498    
499    /**
500     * Compute limit cap (maximum number of students that can get into the offering using this reservation) for a particular configuration
501     * @return reservation limit cap
502     */
503    public double getLimitCap(Config config) {
504        Double cap = (iConfigLimitCap == null ? null : iConfigLimitCap.get(config.getId()));
505        if (cap == null) {
506            cap = getLimitCapNoCache(config);
507            if (iConfigLimitCap == null) iConfigLimitCap = new HashMap<Long, Double>();
508            iConfigLimitCap.put(config.getId(), cap);
509        }
510        return cap;
511    }
512    
513    private double getLimitCapNoCache(Config config) {
514        if (getConfigs().isEmpty() && getSections().isEmpty()) return -1; // no config -> can be unlimited
515        
516        if (canAssignOverLimit()) return -1; // can assign over limit -> no cap
517
518        if (areRestrictionsInclusive()) {
519            // no restrictions for this configuration -> no limit
520            if (!getConfigs().contains(config)) return 0;
521            
522            // config cap
523            double configCap = config.getLimit();
524        
525            for (Map.Entry<Subpart, Set<Section>> entry: getSections().entrySet()) {
526                if (!config.equals(entry.getKey().getConfig())) continue;
527                Set<Section> sections = entry.getValue();
528                
529                // subpart cap
530                double subpartCap = 0;
531                for (Section section: sections)
532                    subpartCap = add(subpartCap, section.getLimit());
533        
534                // minimize
535                configCap = min(configCap, subpartCap);
536            }
537            
538            // add config cap
539            return configCap;
540        } else {
541            double cap = 0;
542            
543            // config cap
544            if (getConfigs().contains(config))
545                cap = add(cap, config.getLimit());
546            
547            // for each subpart
548            for (Map.Entry<Subpart, Set<Section>> entry: getSections().entrySet()) {
549                if (!config.equals(entry.getKey().getConfig())) continue;
550                Set<Section> sections = entry.getValue();
551
552                // subpart cap
553                double subpartCap = 0;
554                for (Section section: sections)
555                    subpartCap = add(subpartCap, section.getLimit());
556                cap = add(cap, subpartCap);
557            }
558            
559            return cap;
560        }
561        
562    }
563    
564    /**
565     * Clear limit cap cache
566     */
567    private void clearLimitCapCache() {
568        iLimitCap = null;
569        if (iConfigLimitCap != null) iConfigLimitCap.clear();
570    }
571    
572    /**
573     * Reservation limit capped the limit cap (see {@link Reservation#getLimitCap()})
574     * @return reservation limit, -1 if unlimited
575     */
576    public double getLimit() {
577        return min(getLimitCap(), getReservationLimit());
578    }
579    
580    /**
581     * Reservation limit capped the limit cap (see {@link Reservation#getLimitCap(Config)}) for a particular configuration
582     * @param config configuration for which the limit is computed (restrictions on other configurations are ignored)
583     * @return reservation limit, -1 if unlimited
584     */
585    public double getLimit(Config config) {
586        return min(getLimitCap(config), getReservationLimit());
587    }
588    
589    /**
590     * True if holding this reservation allows a student to have attend overlapping class. 
591     * @return does this reservation allow for overlaps
592     */
593    public boolean isAllowOverlap() {
594        return iAllowOverlap;
595    }
596    
597    /**
598     * Set to true if holding this reservation allows a student to have attend overlapping class.
599     * @param allowOverlap does this reservation allow for overlaps
600     */
601    public void setAllowOverlap(boolean allowOverlap) {
602        iAllowOverlap = allowOverlap;
603    }
604    
605    /**
606     * True if holding this reservation allows a student to attend a disabled class. 
607     * @return does this reservation allow for disabled sections
608     */
609    public boolean isAllowDisabled() {
610        return iAllowDisabled;
611    }
612    
613    /**
614     * Set to true if holding this reservation allows a student to attend a disabled class
615     * @param allowDisabled does this reservation allow for disabled sections
616     */
617    public void setAllowDisabled(boolean allowDisabled) {
618        iAllowDisabled = allowDisabled;
619    }
620    
621    /**
622     * Set reservation expiration. If a reservation is expired, it works as ordinary reservation
623     * (especially the flags mutBeUsed and isAllowOverlap), except it does not block other students
624     * of getting into the offering / config / section.  
625     * @param expired is this reservation expired
626     */
627    public void setExpired(boolean expired) {
628        iExpired = expired;
629    }
630    
631    /**
632     * True if the reservation is expired. If a reservation is expired, it works as ordinary reservation
633     * (especially the flags mutBeUsed and isAllowOverlap), except it does not block other students
634     * of getting into the offering / config / section.
635     * @return is this reservation expired
636     */
637    public boolean isExpired() {
638        return iExpired;
639    }
640    
641    /**
642     * No enrollment is matching this reservation when set to true
643     */
644    public boolean neverIncluded() { return iNeverIncluded; }
645    
646    /**
647     * No enrollment is matching this reservation when set to true
648     */
649    public void setNeverIncluded(boolean neverIncluded) { iNeverIncluded = neverIncluded; }
650    
651    /**
652     * Can break linked-section constraints when set to true
653     */
654    public boolean canBreakLinkedSections() { return iBreakLinkedSections; }
655    
656    /**
657     * Can break linked-section constraints when set to true
658     */
659    public void setBreakLinkedSections(boolean breakLinkedSections) { iBreakLinkedSections = breakLinkedSections; }
660    
661    
662    @Override
663    public Model<Request, Enrollment> getModel() {
664        return getOffering().getModel();
665    }
666    
667    /**
668     * Available reserved space
669     * @param assignment current assignment
670     * @param excludeRequest excluding given request (if not null)
671     * @return available reserved space
672     **/
673    public double getReservedAvailableSpace(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
674        return getContext(assignment).getReservedAvailableSpace(assignment, excludeRequest);
675    }
676    
677    /**
678     * Available reserved space for a particular config
679     * @param assignment current assignment
680     * @param excludeRequest excluding given request (if not null)
681     * @return available reserved space
682     **/
683    public double getReservedAvailableSpace(Assignment<Request, Enrollment> assignment, Config config, Request excludeRequest) {
684        return getContext(assignment).getReservedAvailableSpace(assignment, config, excludeRequest);
685    }
686    
687    /** Enrollments assigned using this reservation 
688     * @param assignment current assignment
689     * @return assigned enrollments of this reservation
690     **/
691    public Set<Enrollment> getEnrollments(Assignment<Request, Enrollment> assignment) {
692        return getContext(assignment).getEnrollments();
693    }
694
695    @Override
696    public ReservationContext createAssignmentContext(Assignment<Request, Enrollment> assignment) {
697        return new ReservationContext(assignment);
698    }
699    
700
701    @Override
702    public ReservationContext inheritAssignmentContext(Assignment<Request, Enrollment> assignment, ReservationContext parentContext) {
703        return new ReservationContext(parentContext);
704    }
705
706    
707    public class ReservationContext implements AssignmentConstraintContext<Request, Enrollment> {
708        /** Enrollments included in this reservation */
709        private Set<Enrollment> iEnrollments = new HashSet<Enrollment>();
710        
711        /** Used part of the limit */
712        private double iUsed = 0;
713        private Map<Long, Double> iUsedByConfig = new HashMap<Long, Double>();
714        private boolean iReadOnly = false;
715
716        public ReservationContext(Assignment<Request, Enrollment> assignment) {
717            for (Course course: getOffering().getCourses())
718                for (CourseRequest request: course.getRequests()) {
719                    Enrollment enrollment = assignment.getValue(request);
720                    if (enrollment != null && Reservation.this.equals(enrollment.getReservation()))
721                        assigned(assignment, enrollment);
722                }
723        }
724        
725        public ReservationContext(ReservationContext parent) {
726            iUsed = parent.iUsed;
727            iUsedByConfig = new HashMap<Long, Double>(parent.iUsedByConfig);
728            iEnrollments = parent.iEnrollments;
729            iReadOnly = true;
730        }
731
732        /** Notify reservation about an unassignment */
733        @Override
734        public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
735            if (iReadOnly) {
736                iEnrollments = new HashSet<Enrollment>(iEnrollments);
737                iReadOnly = false;
738            }
739            if (iEnrollments.add(enrollment)) {
740                iUsed += enrollment.getRequest().getWeight();
741                Double used = iUsedByConfig.get(enrollment.getConfig().getId());
742                iUsedByConfig.put(enrollment.getConfig().getId(), enrollment.getRequest().getWeight() + (used == null ? 0.0 : used.doubleValue()));
743            }
744        }
745
746        /** Notify reservation about an assignment */
747        @Override
748        public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
749            if (iReadOnly) {
750                iEnrollments = new HashSet<Enrollment>(iEnrollments);
751                iReadOnly = false;
752            }
753            if (iEnrollments.remove(enrollment)) {
754                iUsed -= enrollment.getRequest().getWeight();
755                Double used = iUsedByConfig.get(enrollment.getConfig().getId());
756                iUsedByConfig.put(enrollment.getConfig().getId(), (used == null ? 0.0 : used.doubleValue()) - enrollment.getRequest().getWeight());
757            }
758        }
759        
760        /** Enrollments assigned using this reservation 
761         * @return assigned enrollments of this reservation
762         **/
763        public Set<Enrollment> getEnrollments() {
764            return iEnrollments;
765        }
766        
767        /** Used space 
768         * @return spaced used of this reservation
769         **/
770        public double getUsedSpace() {
771            return iUsed;
772        }
773        
774        /** Used space in a particular config
775         * @return spaced used of this reservation
776         **/
777        public double getUsedSpace(Config config) {
778            Double used = iUsedByConfig.get(config.getId());
779            return (used == null ? 0.0 : used.doubleValue());
780        }
781        
782        /**
783         * Available reserved space
784         * @param assignment current assignment
785         * @param excludeRequest excluding given request (if not null)
786         * @return available reserved space
787         **/
788        public double getReservedAvailableSpace(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
789            // Unlimited
790            if (getLimit() < 0) return Double.MAX_VALUE;
791            
792            double reserved = getLimit() - getContext(assignment).getUsedSpace();
793            if (excludeRequest != null && assignment.getValue(excludeRequest) != null && iEnrollments.contains(assignment.getValue(excludeRequest)))
794                reserved += excludeRequest.getWeight();
795            
796            return reserved;
797        }
798        
799        /**
800         * Available reserved space for a particular config
801         * @param assignment current assignment
802         * @param excludeRequest excluding given request (if not null)
803         * @return available reserved space
804         **/
805        public double getReservedAvailableSpace(Assignment<Request, Enrollment> assignment, Config config, Request excludeRequest) {
806            if (config == null) return getReservedAvailableSpace(assignment, excludeRequest);
807            
808            // Unlimited
809            if (getLimit(config) < 0) return Double.MAX_VALUE;
810            
811            double reserved = getLimit(config) - getContext(assignment).getUsedSpace(config);
812            if (excludeRequest != null && assignment.getValue(excludeRequest) != null && assignment.getValue(excludeRequest).getConfig().equals(config) && iEnrollments.contains(assignment.getValue(excludeRequest)))
813                reserved += excludeRequest.getWeight();
814            
815            return reserved;
816        }
817    }
818}