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}