    001package net.sf.cpsolver.studentsct.model;
    002
    003import java.util.ArrayList;
    004import java.util.HashSet;
    005import java.util.List;
    006import java.util.Set;
    007
    008import 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 Tomáš Müller<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 */
    038public 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}