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