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