    001package org.cpsolver.studentsct.model;
    002
    003import java.util.ArrayList;
    004import java.util.BitSet;
    005import java.util.HashSet;
    006import java.util.Iterator;
    007import java.util.List;
    008import java.util.Set;
    009
    010import org.cpsolver.coursett.model.TimeLocation;
    011import org.cpsolver.ifs.util.ToolBox;
    012
    013
    014/**
    015 * Student choice. Students have a choice of availabe time (but not room) and
    016 * instructor(s).
    017 *
    018 * Choices of subparts that have the same instrutional type are also merged
    019 * together. For instance, a student have a choice of a time/instructor of a
    020 * Lecture and of a Recitation.
    021 *
    022 * <br>
    023 * <br>
    024 *
    025 * @version StudentSct 1.3 (Student Sectioning)<br>
    026 *          Copyright (C) 2007 - 2014 Tomáš Müller<br>
    027 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    028 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    029 * <br>
    030 *          This library is free software; you can redistribute it and/or modify
    031 *          it under the terms of the GNU Lesser General Public License as
    032 *          published by the Free Software Foundation; either version 3 of the
    033 *          License, or (at your option) any later version. <br>
    034 * <br>
    035 *          This library is distributed in the hope that it will be useful, but
    036 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    037 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    038 *          Lesser General Public License for more details. <br>
    039 * <br>
    040 *          You should have received a copy of the GNU Lesser General Public
    041 *          License along with this library; if not see
    042 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    043 */
    044public class Choice {
    045    private Long iSectionId = null;
    046    private Long iSubpartId = null;
    047    private Long iConfigId = null;
    048    private Offering iOffering = null;
    049    private String iInstructionalType = null;
    050    private TimeLocation iTime = null;
    051    private List<Instructor> iInstructors = null;
    052    private int iHashCode;
    053
    054    /**
    055     * Constructor
    056     *
    057     * @param offering
    058     *            instructional offering to which the choice belongs
    059     * @param instructionalType
    060     *            instructional type to which the choice belongs (e.g., Lecture,
    061     *            Recitation or Laboratory)
    062     * @param time
    063     *            time assignment
    064     * @param instructors
    065     *            instructor(s)
    066     */
    067    public Choice(Offering offering, String instructionalType, TimeLocation time, List<Instructor> instructors) {
    068        iOffering = offering;
    069        iInstructionalType = instructionalType;
    070        iTime = time;
    071        iInstructors = instructors;
    072        iHashCode = getId().hashCode();
    073    }
    074
    075    @Deprecated
    076    public Choice(Offering offering, String instructionalType, TimeLocation time, String instructorIds, String instructorNames) {
    077        this(offering, instructionalType, time, Instructor.toInstructors(instructorIds, instructorNames));
    078    }
    079
    080    /**
    081     * Constructor
    082     * @param section section to base the choice on
    083     */
    084    public Choice(Section section) {
    085        this(section.getSubpart().getConfig().getOffering(), section.getSubpart().getInstructionalType(), section.getTime(), section.getInstructors());
    086        iSectionId = section.getId();
    087        iSubpartId = section.getSubpart().getId();
    088        // iConfigId = section.getSubpart().getConfig().getId();
    089    }
    090
    091    /**
    092     * Constructor
    093     * @param config configuration to base the choice on
    094     */
    095    public Choice(Config config) {
    096        this(config.getOffering(), "N/A", null, null);
    097        iConfigId = config.getId();
    098    }
    099
    100    /**
    101     * Constructor
    102     *
    103     * @param offering
    104     *            instructional offering to which the choice belongs
    105     * @param choiceId
    106     *            choice id is in format instructionalType|time|instructorIds
    107     *            where time is of format dayCode:startSlot:length:datePatternId
    108     */
    109    public Choice(Offering offering, String choiceId) {
    110        iOffering = offering;
    111        String[] choices = choiceId.split("\\|");
    112        iInstructionalType = choices[0];
    113        if (choices.length > 1 && !choices[1].isEmpty()) {
    114            String[] times = choices[1].split(":");
    115            int dayCode = Integer.parseInt(times[0]);
    116            int startSlot = Integer.parseInt(times[1]);
    117            int length = Integer.parseInt(times[2]);
    118            Long datePatternId = (times.length > 3 ? Long.valueOf(times[3]) : null);
    119            iTime = new TimeLocation(dayCode, startSlot, length, 0, 0, datePatternId, "N/A", new BitSet(), 0);
    120        }
    121        if (choices.length > 2 && !choices[2].isEmpty()) {
    122            iInstructors = new ArrayList<Instructor>();
    123            for (String id: choices[2].split(":"))
    124                iInstructors.add(new Instructor(Long.parseLong(id)));
    125        }
    126        if (choices.length > 3 && !choices[3].isEmpty()) {
    127            String[] ids = choices[3].split(":");
    128            iSectionId = (ids.length < 1 || ids[0].isEmpty() ? null : Long.valueOf(ids[0]));
    129            iSubpartId = (ids.length < 2 || ids[1].isEmpty() ? null : Long.valueOf(ids[1]));
    130            iConfigId = (ids.length < 3 || ids[2].isEmpty() ? null : Long.valueOf(ids[2]));
    131        }
    132        iHashCode = getId().hashCode();
    133    }
    134
    135    /** Instructional offering to which this choice belongs
    136     * @return instructional offering
    137     **/
    138    public Offering getOffering() {
    139        return iOffering;
    140    }
    141
    142    /**
    143     * Instructional type (e.g., Lecture, Recitation or Laboratory) to which
    144     * this choice belongs
    145     * @return instructional type
    146     */
    147    public String getInstructionalType() {
    148        return iInstructionalType;
    149    }
    150
    151    /** Time location of the choice
    152     * @return selected time
    153     **/
    154    public TimeLocation getTime() {
    155        return iTime;
    156    }
    157
    158    /**
    159     * Return true if the given choice has the same instructional type and time
    160     * return true if the two choices have the same time
    161     */
    162    public boolean sameTime(Choice choice) {
    163        return getInstructionalType().equals(choice.getInstructionalType()) && sameTime(getTime(), choice.getTime());
    164    }
    165
    166    private static boolean sameTime(TimeLocation t1, TimeLocation t2) {
    167        if (t1 == null) return (t2 == null);
    168        if (t2 == null) return false;
    169        if (t1.getStartSlot() != t2.getStartSlot()) return false;
    170        if (t1.getLength() != t2.getLength()) return false;
    171        if (t1.getDayCode() != t2.getDayCode()) return false;
    172        return ToolBox.equals(t1.getDatePatternId(), t2.getDatePatternId());
    173    }
    174
    175    /**
    176     * Instructor(s) id of the choice, can be null if the section has no
    177     * instructor assigned
    178     * @return selected instructors
    179     */
    180    @Deprecated
    181    public String getInstructorIds() {
    182        if (hasInstructors()) {
    183            StringBuffer sb = new StringBuffer();
    184            for (Iterator<Instructor> i = getInstructors().iterator(); i.hasNext(); ) {
    185                Instructor instructor = i.next();
    186                sb.append(instructor.getId());
    187                if (i.hasNext()) sb.append(":");
    188            }
    189            return sb.toString();
    190        }
    191        return null;
    192    }
    193
    194    /**
    195     * Instructor(s) name of the choice, can be null if the section has no
    196     * instructor assigned
    197     * @return selected instructors
    198     */
    199    @Deprecated
    200    public String getInstructorNames() {
    201        if (hasInstructors()) {
    202            StringBuffer sb = new StringBuffer();
    203            for (Iterator<Instructor> i = getInstructors().iterator(); i.hasNext(); ) {
    204                Instructor instructor = i.next();
    205                if (instructor.getName() != null)
    206                    sb.append(instructor.getName());
    207                else if (instructor.getExternalId() != null)
    208                    sb.append(instructor.getExternalId());
    209                if (i.hasNext()) sb.append(":");
    210            }
    211            return sb.toString();
    212        }
    213        return null;
    214    }
    215
    216    /**
    217     * Instructor names
    218     * @param delim delimiter
    219     * @return instructor names
    220     */
    221    public String getInstructorNames(String delim) {
    222        if (iInstructors == null || iInstructors.isEmpty()) return "";
    223        StringBuffer sb = new StringBuffer();
    224        for (Iterator<Instructor> i = iInstructors.iterator(); i.hasNext(); ) {
    225            Instructor instructor = i.next();
    226            sb.append(instructor.getName() != null ? instructor.getName() : instructor.getExternalId() != null ? instructor.getExternalId() : "I" + instructor.getId());
    227            if (i.hasNext()) sb.append(delim);
    228        }
    229        return sb.toString();
    230    }
    231
    232    /**
    233     * Choice id combined from instructionalType, time and instructorIds in the
    234     * following format: instructionalType|time|instructorIds where time is of
    235     * format dayCode:startSlot:length:datePatternId
    236     * @return choice id
    237     */
    238    public String getId() {
    239        String ret = getInstructionalType() + "|";
    240        if (getTime() != null)
    241            ret += getTime().getDayCode() + ":" + getTime().getStartSlot() + ":" + getTime().getLength() + (getTime().getDatePatternId() == null ? "" : ":" + getTime().getDatePatternId());
    242        ret += "|" + (hasInstructors() ? getInstructorIds() : "");
    243        ret += "|" + (iSectionId == null ? "" : iSectionId) + ":" + (iSubpartId == null ? "" : iSubpartId) + ":" + (iConfigId == null ? "" : iConfigId);
    244        return ret;
    245    }
    246
    247    /** Compare two choices, based on {@link Choice#getId()} */
    248    @Override
    249    public boolean equals(Object o) {
    250        if (o == null || !(o instanceof Choice))
    251            return false;
    252        return ((Choice) o).getId().equals(getId());
    253    }
    254
    255    /** Choice hash id, based on {@link Choice#getId()} */
    256    @Override
    257    public int hashCode() {
    258        return iHashCode;
    259    }
    260
    261    /**
    262     * List of sections of the instructional offering which represent this
    263     * choice. Note that there can be multiple sections with the same choice
    264     * (e.g., only if the room location differs).
    265     * @return set of sections for matching this choice
    266     */
    267    public Set<Section> getSections() {
    268        Set<Section> sections = new HashSet<Section>();
    269        for (Config config : getOffering().getConfigs()) {
    270            for (Subpart subpart : config.getSubparts()) {
    271                if (!subpart.getInstructionalType().equals(getInstructionalType()))
    272                    continue;
    273                for (Section section : subpart.getSections()) {
    274                    if (this.sameChoice(section))
    275                        sections.add(section);
    276                }
    277            }
    278        }
    279        return sections;
    280    }
    281
    282    /**
    283     * List of parent sections of sections of the instructional offering which
    284     * represent this choice. Note that there can be multiple sections with the
    285     * same choice (e.g., only if the room location differs).
    286     * @return set of parent sections
    287     */
    288    public Set<Section> getParentSections() {
    289        Set<Section> parentSections = new HashSet<Section>();
    290        for (Config config : getOffering().getConfigs()) {
    291            for (Subpart subpart : config.getSubparts()) {
    292                if (!subpart.getInstructionalType().equals(getInstructionalType()))
    293                    continue;
    294                if (subpart.getParent() == null)
    295                    continue;
    296                for (Section section : subpart.getSections()) {
    297                    if (this.sameChoice(section) && section.getParent() != null)
    298                        parentSections.add(section.getParent());
    299                }
    300            }
    301        }
    302        return parentSections;
    303    }
    304
    305    /**
    306     * Choice name: name of the appropriate subpart + long name of time +
    307     * instructor(s) name
    308     * @return choice name
    309     */
    310    public String getName() {
    311        return (getOffering().getSubparts(getInstructionalType()).iterator().next()).getName()
    312                + " "
    313                + (getTime() == null ? "" : getTime().getLongName(true))
    314                + (hasInstructors() ? " " + getInstructorNames(",") : "");
    315    }
    316
    317    /** True if the instructional type is the same */
    318    public boolean sameInstructionalType(Section section) {
    319        return getInstructionalType() != null && getInstructionalType().equals(section.getSubpart().getInstructionalType());
    320    }
    321
    322    /** True if the time assignment is the same */
    323    public boolean sameTime(Section section) {
    324        return sameTime(getTime(), section.getTime());
    325    }
    326
    327    /** True if the section contains all instructors of this choice */
    328    public boolean sameInstructors(Section section) {
    329        return !hasInstructors() || (section.hasInstructors() && section.getInstructors().containsAll(getInstructors()));
    330    }
    331
    332    /** True if the offering is the same */
    333    public boolean sameOffering(Section section) {
    334        return iOffering != null && iOffering.equals(section.getSubpart().getConfig().getOffering());
    335    }
    336
    337    /** True if the time assignment as well as the instructor(s) are the same */
    338    public boolean sameChoice(Section section) {
    339        return sameOffering(section) && sameInstructionalType(section) && sameTime(section) && sameInstructors(section);
    340    }
    341
    342    /** True if the section is the very same */
    343    public boolean sameSection(Section section) {
    344        return iSectionId != null && iSectionId.equals(section.getId());
    345    }
    346
    347    /** True if the subpart is the very same */
    348    public boolean sameSubart(Section section) {
    349        return iSubpartId != null && iSubpartId.equals(section.getSubpart().getId());
    350    }
    351
    352    /** True if the configuration is the very same */
    353    public boolean sameConfiguration(Section section) {
    354        return iConfigId != null && iConfigId.equals(section.getSubpart().getConfig().getId());
    355    }
    356
    357    /** True if the configuration is the very same */
    358    public boolean sameConfiguration(Enrollment enrollment) {
    359        return iConfigId != null && enrollment.getConfig() != null && iConfigId.equals(enrollment.getConfig().getId());
    360    }
    361
    362    /** True if the configuration is the very same */
    363    public boolean sameSection(Enrollment enrollment) {
    364        if (iSectionId == null || !enrollment.isCourseRequest()) return false;
    365        for (Section section: enrollment.getSections())
    366            if (iSectionId.equals(section.getId())) return true;
    367        return false;
    368    }
    369
    370    /** True if this choice is applicable to the given section (that is, the choice is a config choice or with the same subpart / instructional type) */
    371    public boolean isMatching(Section section) {
    372        if (iConfigId != null) return true;
    373        if (iSubpartId != null && iSubpartId.equals(section.getSubpart().getId())) return true;
    374        if (iSubpartId == null && iInstructionalType != null && iInstructionalType.equals(section.getSubpart().getInstructionalType())) return true;
    375        return false;
    376    }
    377
    378    /** section id */
    379    public Long getSectionId() { return iSectionId; }
    380    /** subpart id */
    381    public Long getSubpartId() { return iSubpartId; }
    382    /** config id */
    383    public Long getConfigId() { return iConfigId; }
    384
    385    @Override
    386    public String toString() {
    387        return getName();
    388    }
    389
    390    /** Instructors of this choice
    391     * @return list of instructors
    392     **/
    393    public List<Instructor> getInstructors() {
    394        return iInstructors;
    395    }
    396
    397    /**
    398     * Has any instructors
    399     * @return return true if there is at least one instructor in this choice
    400     */
    401    public boolean hasInstructors() {
    402        return iInstructors != null && !iInstructors.isEmpty();
    403    }
    404
    405    /**
    406     * Return number of instructors of this choice
    407     * @return number of instructors of this choice
    408     */
    409    public int nrInstructors() {
    410        return iInstructors == null ? 0 : iInstructors.size();
    411    }
    412}