    001package org.cpsolver.studentsct.model;
    002
    003import java.util.ArrayList;
    004import java.util.HashSet;
    005import java.util.List;
    006import java.util.Set;
    007import java.util.regex.Matcher;
    008import java.util.regex.Pattern;
    009
    010import org.cpsolver.studentsct.reservation.Reservation;
    011
    012
    013/**
    014 * Representation of a scheduling subpart. Each scheduling subpart contains id,
    015 * instructional type, name, instructional offering configuration, and a list of
    016 * sections. Optionally, parent-child relation between subparts can be defined. <br>
    017 * <br>
    018 *
    019 * @author  Tomáš Müller
    020 * @version StudentSct 1.3 (Student Sectioning)<br>
    021 *          Copyright (C) 2007 - 2014 Tomáš Müller<br>
    022 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    023 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    024 * <br>
    025 *          This library is free software; you can redistribute it and/or modify
    026 *          it under the terms of the GNU Lesser General Public License as
    027 *          published by the Free Software Foundation; either version 3 of the
    028 *          License, or (at your option) any later version. <br>
    029 * <br>
    030 *          This library is distributed in the hope that it will be useful, but
    031 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    032 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    033 *          Lesser General Public License for more details. <br>
    034 * <br>
    035 *          You should have received a copy of the GNU Lesser General Public
    036 *          License along with this library; if not see
    037 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    038 */
    039public class Subpart implements Comparable<Subpart> {
    040    private long iId = -1;
    041    private String iInstructionalType = null;
    042    private String iName = null;
    043    private List<Section> iSections = new ArrayList<Section>();
    044    private Config iConfig = null;
    045    private Subpart iParent = null;
    046    private boolean iAllowOverlap = false;
    047    private String iCredit = null;
    048    private Float iCreditValue = null;
    049
    050    /**
    051     * Constructor
    052     *
    053     * @param id
    054     *            scheduling subpart unique id
    055     * @param itype
    056     *            instructional type
    057     * @param name
    058     *            subpart name
    059     * @param config
    060     *            instructional offering configuration to which this subpart
    061     *            belongs
    062     * @param parent
    063     *            parent subpart, if parent-child relation is defined between
    064     *            subparts
    065     */
    066    public Subpart(long id, String itype, String name, Config config, Subpart parent) {
    067        iId = id;
    068        iInstructionalType = itype;
    069        iName = name;
    070        iConfig = config;
    071        iParent = parent;
    072        iConfig.getSubparts().add(this);
    073    }
    074
    075    /** Subpart id
    076     * @return scheduling subpart unique id
    077     **/
    078    public long getId() {
    079        return iId;
    080    }
    081
    082    /** Instructional type, e.g., Lecture, Recitation or Laboratory
    083     * @return instructional type
    084     **/
    085    public String getInstructionalType() {
    086        return iInstructionalType;
    087    }
    088
    089    /** Subpart name
    090     * @return scheduling subpart name
    091     **/
    092    public String getName() {
    093        return iName;
    094    }
    095
    096    /** Instructional offering configuration to which this subpart belongs
    097     * @return instructional offering configuration
    098     **/
    099    public Config getConfig() {
    100        return iConfig;
    101    }
    102
    103    /** List of sections
    104     * @return classes of this scheduling supart
    105     **/
    106    public List<Section> getSections() {
    107        return iSections;
    108    }
    109
    110    /** Parent subpart, if parent-child relation is defined between subparts
    111     * @return parent scheduling subpart
    112     **/
    113    public Subpart getParent() {
    114        return iParent;
    115    }
    116
    117    @Override
    118    public String toString() {
    119        return getName();
    120    }
    121
    122    /**
    123     * True, if this subpart is parent (or parent of a parent etc.) of the given
    124     * subpart
    125     * @param subpart parent scheduling subpart
    126     * @return true if parent (even indirect)
    127     */
    128    public boolean isParentOf(Subpart subpart) {
    129        if (subpart.getParent() == null)
    130            return false;
    131        if (subpart.getParent().equals(this))
    132            return true;
    133        return isParentOf(subpart.getParent());
    134    }
    135
    136    /**
    137     * Compare two subparts: put parents first, use ids if there is no
    138     * parent-child relation
    139     */
    140    @Override
    141    public int compareTo(Subpart s) {
    142        if (isParentOf(s))
    143            return -1;
    144        if (s.isParentOf(this))
    145            return 1;
    146        int cmp = getInstructionalType().compareTo(s.getInstructionalType());
    147        if (cmp != 0)
    148            return cmp;
    149        return Double.compare(getId(), s.getId());
    150    }
    151
    152    /** List of available choices of the sections of this subpart.
    153     * @return set of available choices
    154     **/
    155    public Set<Choice> getChoices() {
    156        Set<Choice> choices = new HashSet<Choice>();
    157        for (Section section : getSections()) {
    158            choices.add(new Choice(section));
    159        }
    160        return choices;
    161    }
    162
    163    /** Minimal penalty from {@link Section#getPenalty()}
    164     * @return minimal penalty
    165     **/
    166    public double getMinPenalty() {
    167        double min = Double.MAX_VALUE;
    168        for (Section section : getSections()) {
    169            min = Math.min(min, section.getPenalty());
    170        }
    171        return (min == Double.MAX_VALUE ? 0.0 : min);
    172    }
    173
    174    /** Maximal penalty from {@link Section#getPenalty()}
    175     * @return maximal penalty
    176     **/
    177    public double getMaxPenalty() {
    178        double max = Double.MIN_VALUE;
    179        for (Section section : getSections()) {
    180            max = Math.max(max, section.getPenalty());
    181        }
    182        return (max == Double.MIN_VALUE ? 0.0 : max);
    183    }
    184
    185    /** Return children subparts
    186     * @return children scheduling subparts
    187     **/
    188    public List<Subpart> getChildren() {
    189        List<Subpart> ret = new ArrayList<Subpart>(getConfig().getSubparts().size());
    190        for (Subpart s : getConfig().getSubparts()) {
    191            if (this.equals(s.getParent()))
    192                ret.add(s);
    193        }
    194        return ret;
    195    }
    196
    197    /** Return true if overlaps are allowed, but the number of overlapping slots should be minimized.
    198     * @return true if overlaps of classes of this scheduling subpart are allowed
    199     **/
    200    public boolean isAllowOverlap() {
    201        return iAllowOverlap;
    202    }
    203
    204    /** Set to true if overlaps are allowed, but the number of overlapping slots should be minimized (defaults to false).
    205     * @param allowOverlap are overlaps of classes of this scheduling subpart allowed
    206     **/
    207    public void setAllowOverlap(boolean allowOverlap) {
    208        iAllowOverlap = allowOverlap;
    209    }
    210
    211    /**
    212     * Get reservations that require sections of this subpart
    213     * @return reservations that require a class of this scheduling subpart
    214     */
    215    public synchronized List<Reservation> getSectionReservations() {
    216        if (iSectionReservations == null) {
    217            iSectionReservations = new ArrayList<Reservation>();
    218            for (Reservation r: getConfig().getOffering().getReservations()) {
    219                if (r.getSections(this) != null)
    220                    iSectionReservations.add(r);
    221            }
    222        }
    223        return iSectionReservations;
    224    }
    225    private List<Reservation> iSectionReservations = null;
    226
    227    /**
    228     * Clear reservation information that was cached on this subpart or below
    229     */
    230    public synchronized void clearReservationCache() {
    231        for (Section s: getSections())
    232            s.clearReservationCache();
    233        iSectionReservations = null;
    234    }
    235
    236    /**
    237     * Sum of the section limits (unlimited, if one or more sections are unlimited)
    238     * @return total class limit
    239     */
    240    public int getLimit() {
    241        if (iLimit == null)
    242            iLimit = getLimitNoCache();
    243        return iLimit;
    244    }
    245    private int getLimitNoCache() {
    246        int limit = 0;
    247        for (Section section: getSections()) {
    248            if (section.getLimit() < 0) return -1;
    249            limit += section.getLimit();
    250        }
    251        return limit;
    252    }
    253    private Integer iLimit = null;
    254
    255    @Override
    256    public boolean equals(Object o) {
    257        if (o == null || !(o instanceof Subpart)) return false;
    258        return getId() == ((Subpart)o).getId();
    259    }
    260
    261    @Override
    262    public int hashCode() {
    263        return (int) (iId ^ (iId >>> 32));
    264    }
    265
    266    /**
    267     * Set credit (Online Student Scheduling only)
    268     * @param credit scheduling subpart credit
    269     */
    270    public void setCredit(String credit) {
    271        iCredit = credit;
    272        if (iCreditValue == null && credit != null) {
    273            int split = credit.indexOf('|');
    274            String abbv = null;
    275            if (split >= 0) {
    276                abbv = credit.substring(0, split);
    277            } else {
    278                abbv = credit;
    279            }
    280            Matcher m = Pattern.compile("(^| )(\\d+\\.?\\d*)([,-]?(\\d+\\.?\\d*))?($| )").matcher(abbv);
    281            if (m.find())
    282                iCreditValue = Float.parseFloat(m.group(2));
    283        }
    284    }
    285
    286    /**
    287     * Get credit (Online Student Scheduling only)
    288     * @return scheduling subpart credit
    289     */
    290    public String getCredit() { return iCredit; }
    291
    292    /**
    293     * True if this subpart has a credit value defined
    294     * @return true if a credit value is set
    295     */
    296    public boolean hasCreditValue() { return iCreditValue != null; }
    297
    298    /**
    299     * Set subpart's credit value (null if not set)
    300     * @param creditValue subpart's credit value
    301     */
    302    public void setCreditValue(Float creditValue) { iCreditValue = creditValue; }
    303
    304    /**
    305     * Get subpart's credit value (null if not set)
    306     * return subpart's credit value
    307     */
    308    public Float getCreditValue() { return iCreditValue; }
    309
    310    public boolean isOnline() {
    311        for (Section section: getSections())
    312            if (!section.isOnline()) return false;
    313        return true;
    314    }
    315
    316    public boolean hasTime() {
    317        for (Section section: getSections())
    318            if (section.hasTime()) return true;
    319        return false;
    320    }
    321
    322    public boolean isPast() {
    323        for (Section section: getSections())
    324            if (!section.isPast()) return false;
    325        return true;
    326    }
    327}