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}