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