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