001    package net.sf.cpsolver.studentsct.model;
002    
003    import java.util.ArrayList;
004    import java.util.HashSet;
005    import java.util.List;
006    import java.util.Set;
007    
008    import net.sf.cpsolver.studentsct.reservation.Reservation;
009    
010    
011    /**
012     * Representation of an instructional offering. An offering contains id, name,
013     * the list of course offerings, and the list of possible configurations. See
014     * {@link Config} and {@link Course}.
015     * 
016     * <br>
017     * <br>
018     * 
019     * @version StudentSct 1.2 (Student Sectioning)<br>
020     *          Copyright (C) 2007 - 2010 Tomas Muller<br>
021     *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
022     *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
023     * <br>
024     *          This library is free software; you can redistribute it and/or modify
025     *          it under the terms of the GNU Lesser General Public License as
026     *          published by the Free Software Foundation; either version 3 of the
027     *          License, or (at your option) any later version. <br>
028     * <br>
029     *          This library is distributed in the hope that it will be useful, but
030     *          WITHOUT ANY WARRANTY; without even the implied warranty of
031     *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
032     *          Lesser General Public License for more details. <br>
033     * <br>
034     *          You should have received a copy of the GNU Lesser General Public
035     *          License along with this library; if not see
036     *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
037     */
038    public class Offering {
039        private long iId = -1;
040        private String iName = null;
041        private List<Config> iConfigs = new ArrayList<Config>();
042        private List<Course> iCourses = new ArrayList<Course>();
043        private List<Reservation> iReservations = new ArrayList<Reservation>();
044    
045        /**
046         * Constructor
047         * 
048         * @param id
049         *            instructional offering unique id
050         * @param name
051         *            instructional offering name (this is usually the name of the
052         *            controlling course)
053         */
054        public Offering(long id, String name) {
055            iId = id;
056            iName = name;
057        }
058    
059        /** Offering id */
060        public long getId() {
061            return iId;
062        }
063    
064        /** Offering name */
065        public String getName() {
066            return iName;
067        }
068    
069        /** Possible configurations */
070        public List<Config> getConfigs() {
071            return iConfigs;
072        }
073    
074        /**
075         * List of courses. One instructional offering can contain multiple courses
076         * (names under which it is offered)
077         */
078        public List<Course> getCourses() {
079            return iCourses;
080        }
081    
082        /**
083         * Return section of the given id, if it is part of one of this offering
084         * configurations.
085         */
086        public Section getSection(long sectionId) {
087            for (Config config : getConfigs()) {
088                for (Subpart subpart : config.getSubparts()) {
089                    for (Section section : subpart.getSections()) {
090                        if (section.getId() == sectionId)
091                            return section;
092                    }
093                }
094            }
095            return null;
096        }
097    
098        /** Return course, under which the given student enrolls into this offering. */
099        public Course getCourse(Student student) {
100            if (getCourses().isEmpty())
101                return null;
102            if (getCourses().size() == 1)
103                return getCourses().get(0);
104            for (Request request : student.getRequests()) {
105                if (request instanceof CourseRequest) {
106                    for (Course course : ((CourseRequest) request).getCourses()) {
107                        if (getCourses().contains(course))
108                            return course;
109                    }
110                }
111            }
112            return getCourses().get(0);
113        }
114    
115        /** Return set of instructional types, union over all configurations. */
116        public Set<String> getInstructionalTypes() {
117            Set<String> instructionalTypes = new HashSet<String>();
118            for (Config config : getConfigs()) {
119                for (Subpart subpart : config.getSubparts()) {
120                    instructionalTypes.add(subpart.getInstructionalType());
121                }
122            }
123            return instructionalTypes;
124        }
125    
126        /**
127         * Return the list of all possible choices of the given instructional type
128         * for this offering.
129         */
130        public Set<Choice> getChoices(String instructionalType) {
131            Set<Choice> choices = new HashSet<Choice>();
132            for (Config config : getConfigs()) {
133                for (Subpart subpart : config.getSubparts()) {
134                    if (!instructionalType.equals(subpart.getInstructionalType()))
135                        continue;
136                    choices.addAll(subpart.getChoices());
137                }
138            }
139            return choices;
140        }
141    
142        /**
143         * Return list of all subparts of the given isntructional type for this
144         * offering.
145         */
146        public Set<Subpart> getSubparts(String instructionalType) {
147            Set<Subpart> subparts = new HashSet<Subpart>();
148            for (Config config : getConfigs()) {
149                for (Subpart subpart : config.getSubparts()) {
150                    if (instructionalType.equals(subpart.getInstructionalType()))
151                        subparts.add(subpart);
152                }
153            }
154            return subparts;
155        }
156    
157        /** Minimal penalty from {@link Config#getMinPenalty()} */
158        public double getMinPenalty() {
159            double min = Double.MAX_VALUE;
160            for (Config config : getConfigs()) {
161                min = Math.min(min, config.getMinPenalty());
162            }
163            return (min == Double.MAX_VALUE ? 0.0 : min);
164        }
165    
166        /** Maximal penalty from {@link Config#getMaxPenalty()} */
167        public double getMaxPenalty() {
168            double max = Double.MIN_VALUE;
169            for (Config config : getConfigs()) {
170                max = Math.max(max, config.getMaxPenalty());
171            }
172            return (max == Double.MIN_VALUE ? 0.0 : max);
173        }
174    
175        @Override
176        public String toString() {
177            return iName;
178        }
179        
180        /** Reservations associated with this offering */
181        public List<Reservation> getReservations() { return iReservations; }
182        
183        /** True if there are reservations for this offering */
184        public boolean hasReservations() { return !iReservations.isEmpty(); }
185        
186        /**
187         * Total space in the offering that is not reserved by any reservation 
188         **/
189        public double getTotalUnreservedSpace() {
190            if (iTotalUnreservedSpace == null)
191                iTotalUnreservedSpace = getTotalUnreservedSpaceNoCache();
192            return iTotalUnreservedSpace;
193        }
194        Double iTotalUnreservedSpace = null;
195        private double getTotalUnreservedSpaceNoCache() {
196            // compute overall available space
197            double available = 0.0;
198            for (Config config: getConfigs()) {
199                available += config.getLimit();
200                // offering is unlimited -> there is unreserved space unless there is an unlimited reservation too 
201                // (in which case there is no unreserved space)
202                if (config.getLimit() < 0) {
203                    for (Reservation r: getReservations()) {
204                        // ignore expired reservations
205                        if (r.isExpired()) continue;
206                        // there is an unlimited reservation -> no unreserved space
207                        if (r.getLimit() < 0) return 0.0;
208                    }
209                    return Double.MAX_VALUE;
210                }
211            }
212            
213            // compute maximal reserved space (out of the available space)
214            double reserved = 0;
215            for (Reservation r: getReservations()) {
216                // ignore expired reservations
217                if (r.isExpired()) continue;
218                // unlimited reservation -> no unreserved space
219                if (r.getLimit() < 0) return 0.0;
220                reserved += r.getLimit();
221            }
222            
223            return Math.max(0.0, available - reserved);
224        }
225    
226        /**
227         * Available space in the offering that is not reserved by any reservation 
228         * @param excludeRequest excluding given request (if not null)
229         **/
230        public double getUnreservedSpace(Request excludeRequest) {
231            // compute available space
232            double available = 0.0;
233            for (Config config: getConfigs()) {
234                available += config.getLimit() - config.getEnrollmentWeight(excludeRequest);
235                // offering is unlimited -> there is unreserved space unless there is an unlimited reservation too 
236                // (in which case there is no unreserved space)
237                if (config.getLimit() < 0) {
238                    for (Reservation r: getReservations()) {
239                        // ignore expired reservations
240                        if (r.isExpired()) continue;
241                        // there is an unlimited reservation -> no unreserved space
242                        if (r.getLimit() < 0) return 0.0;
243                    }
244                    return Double.MAX_VALUE;
245                }
246            }
247            
248            // compute reserved space (out of the available space)
249            double reserved = 0;
250            for (Reservation r: getReservations()) {
251                // ignore expired reservations
252                if (r.isExpired()) continue;
253                // unlimited reservation -> no unreserved space
254                if (r.getLimit() < 0) return 0.0;
255                reserved += Math.max(0.0, r.getReservedAvailableSpace(excludeRequest));
256            }
257            
258            return available - reserved;
259        }
260    
261        
262        /**
263         * Clear reservation information that was cached on this offering or below
264         */
265        public void clearReservationCache() {
266            for (Config c: getConfigs())
267                c.clearReservationCache();
268            for (Course c: getCourses())
269                for (CourseRequest r: c.getRequests())
270                    r.clearReservationCache();
271            iTotalUnreservedSpace = null;
272        }
273    
274        @Override
275        public boolean equals(Object o) {
276            if (o == null || !(o instanceof Offering)) return false;
277            return getId() == ((Offering)o).getId();
278        }
279        
280        @Override
281        public int hashCode() {
282            return (int) (iId ^ (iId >>> 32));
283        }
284    
285    }