001package net.sf.cpsolver.coursett.model; 002 003import java.util.BitSet; 004import java.util.Enumeration; 005 006import net.sf.cpsolver.coursett.Constants; 007import net.sf.cpsolver.ifs.util.ToolBox; 008 009/** 010 * Time part of placement. 011 * 012 * @version CourseTT 1.2 (University Course Timetabling)<br> 013 * Copyright (C) 2006 - 2010 Tomáš Müller<br> 014 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 015 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 016 * <br> 017 * This library is free software; you can redistribute it and/or modify 018 * it under the terms of the GNU Lesser General Public License as 019 * published by the Free Software Foundation; either version 3 of the 020 * License, or (at your option) any later version. <br> 021 * <br> 022 * This library is distributed in the hope that it will be useful, but 023 * WITHOUT ANY WARRANTY; without even the implied warranty of 024 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 025 * Lesser General Public License for more details. <br> 026 * <br> 027 * You should have received a copy of the GNU Lesser General Public 028 * License along with this library; if not see 029 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 030 */ 031 032public class TimeLocation { 033 private int iStartSlot; 034 035 private int iPreference; 036 private double iNormalizedPreference; 037 038 private Long iTimePatternId = null; 039 private int iHashCode; 040 041 private int iDayCode; 042 private int iLength; 043 private int iNrMeetings; 044 private int iBreakTime; 045 046 private BitSet iWeekCode; 047 private Long iDatePatternId = null; 048 private String iDatePatternName = null; 049 private int iDatePreference; 050 051 /** 052 * Constructor 053 * 054 * @param dayCode 055 * days (combination of 1 for Monday, 2 for Tuesday, ...) 056 * @param startTime 057 * start slot 058 * @param length 059 * number of slots 060 * @param pref 061 * time preference 062 */ 063 public TimeLocation(int dayCode, int startTime, int length, int pref, double normPref, int datePatternPreference, 064 Long datePatternId, String datePatternName, BitSet weekCode, int breakTime) { 065 iPreference = pref; 066 iNormalizedPreference = normPref; 067 iStartSlot = startTime; 068 iDayCode = dayCode; 069 iLength = length; 070 iBreakTime = breakTime; 071 iNrMeetings = 0; 072 for (int i = 0; i < Constants.DAY_CODES.length; i++) { 073 if ((iDayCode & Constants.DAY_CODES[i]) == 0) 074 continue; 075 iNrMeetings++; 076 } 077 iHashCode = combine(combine(iDayCode, iStartSlot), iLength); 078 iDatePatternName = datePatternName; 079 iWeekCode = weekCode; 080 iDatePatternId = datePatternId; 081 if (iDatePatternName == null) 082 iDatePatternName = "not set"; 083 iDatePreference = datePatternPreference; 084 if (iWeekCode == null) { 085 iWeekCode = new BitSet(366); 086 for (int i = 0; i <= 365; i++) 087 iWeekCode.set(i); 088 } 089 } 090 091 public TimeLocation(int dayCode, int startTime, int length, int pref, double normPref, Long datePatternId, 092 String datePatternName, BitSet weekCode, int breakTime) { 093 this(dayCode, startTime, length, pref, normPref, 0, datePatternId, datePatternName, weekCode, breakTime); 094 } 095 096 /** Number of meetings */ 097 public int getNrMeetings() { 098 return iNrMeetings; 099 } 100 101 public int getBreakTime() { 102 return iBreakTime; 103 } 104 105 public void setBreakTime(int breakTime) { 106 iBreakTime = breakTime; 107 } 108 109 private static int combine(int a, int b) { 110 int ret = 0; 111 for (int i = 0; i < 15; i++) 112 ret = ret | ((a & (1 << i)) << i) | ((b & (1 << i)) << (i + 1)); 113 return ret; 114 } 115 116 /** Days (combination of 1 for Monday, 2 for Tuesday, ...) */ 117 public int getDayCode() { 118 return iDayCode; 119 } 120 121 /** Days for printing purposes */ 122 public String getDayHeader() { 123 StringBuffer sb = new StringBuffer(); 124 for (int i = 0; i < Constants.DAY_CODES.length; i++) 125 if ((iDayCode & Constants.DAY_CODES[i]) != 0) 126 sb.append(Constants.DAY_NAMES_SHORT[i]); 127 return sb.toString(); 128 } 129 130 /** Start time for printing purposes */ 131 public String getStartTimeHeader() { 132 int min = iStartSlot * Constants.SLOT_LENGTH_MIN + Constants.FIRST_SLOT_TIME_MIN; 133 int h = min / 60; 134 int m = min % 60; 135 return (h > 12 ? h - 12 : h) + ":" + (m < 10 ? "0" : "") + m + (h >= 12 ? "p" : "a"); 136 } 137 138 /** End time for printing purposes */ 139 public String getEndTimeHeader() { 140 int min = (iStartSlot + iLength) * Constants.SLOT_LENGTH_MIN + Constants.FIRST_SLOT_TIME_MIN - getBreakTime(); 141 int m = min % 60; 142 int h = min / 60; 143 return (h > 12 ? h - 12 : h) + ":" + (m < 10 ? "0" : "") + m + (h >= 12 ? "p" : "a"); 144 } 145 146 /** End time for printing purposes */ 147 public String getEndTimeHeaderNoAdj() { 148 int min = (iStartSlot + iLength) * Constants.SLOT_LENGTH_MIN + Constants.FIRST_SLOT_TIME_MIN; 149 int m = min % 60; 150 int h = min / 60; 151 return (h > 12 ? h - 12 : h) + ":" + (m < 10 ? "0" : "") + m + (h >= 12 ? "p" : "a"); 152 } 153 154 /** Start slot */ 155 public int getStartSlot() { 156 return iStartSlot; 157 } 158 159 /** Used slots in a day (combination of 1..first, 2..second,...) */ 160 161 /** true if days overlap */ 162 public boolean shareDays(TimeLocation anotherLocation) { 163 return ((iDayCode & anotherLocation.iDayCode) != 0); 164 } 165 166 /** number of overlapping days */ 167 public int nrSharedDays(TimeLocation anotherLocation) { 168 int ret = 0; 169 for (int i = 0; i < Constants.NR_DAYS; i++) { 170 if ((iDayCode & Constants.DAY_CODES[i]) == 0) 171 continue; 172 if ((anotherLocation.iDayCode & Constants.DAY_CODES[i]) == 0) 173 continue; 174 ret++; 175 } 176 return ret; 177 } 178 179 /** true if hours overlap */ 180 public boolean shareHours(TimeLocation anotherLocation) { 181 return (iStartSlot + iLength > anotherLocation.iStartSlot) 182 && (anotherLocation.iStartSlot + anotherLocation.iLength > iStartSlot); 183 } 184 185 /** number of overlapping time slots (ignoring days) */ 186 public int nrSharedHours(TimeLocation anotherLocation) { 187 int end = Math.min(iStartSlot + iLength, anotherLocation.iStartSlot + anotherLocation.iLength); 188 int start = Math.max(iStartSlot, anotherLocation.iStartSlot); 189 return (end < start ? 0 : end - start); 190 } 191 192 /** true if weeks overlap */ 193 public boolean shareWeeks(TimeLocation anotherLocation) { 194 return iWeekCode.intersects(anotherLocation.iWeekCode); 195 } 196 197 /** true if weeks overlap */ 198 public boolean shareWeeks(BitSet weekCode) { 199 return iWeekCode.intersects(weekCode); 200 } 201 202 public boolean hasDay(int day) { 203 return iWeekCode.get(day); 204 } 205 206 /** true if overlap */ 207 public boolean hasIntersection(TimeLocation anotherLocation) { 208 return shareDays(anotherLocation) && shareHours(anotherLocation) && shareWeeks(anotherLocation); 209 } 210 211 /** Used slots */ 212 public IntEnumeration getSlots() { 213 return new SlotsEnum(); 214 } 215 216 /** Used start slots (for each meeting) */ 217 public IntEnumeration getStartSlots() { 218 return new StartSlotsEnum(); 219 } 220 221 /** Days */ 222 public IntEnumeration getDays() { 223 return new DaysEnum(); 224 } 225 226 private int[] iDaysCache = null; 227 public int[] getDaysArray() { 228 if (iDaysCache == null) { 229 iDaysCache = new int[getNrMeetings()]; 230 int i = 0; 231 for (Enumeration<Integer> e = getDays(); e.hasMoreElements();) 232 iDaysCache[i++] = e.nextElement(); 233 } 234 return iDaysCache; 235 } 236 237 /** Text representation */ 238 public String getName() { 239 return getDayHeader() + " " + getStartTimeHeader(); 240 } 241 242 public String getLongName() { 243 return getDayHeader() + " " + getStartTimeHeader() + " - " + getEndTimeHeader() + " " + getDatePatternName(); 244 } 245 246 public String getLongNameNoAdj() { 247 return getDayHeader() + " " + getStartTimeHeader() + " - " + getEndTimeHeaderNoAdj() + " " 248 + getDatePatternName(); 249 } 250 251 /** Preference */ 252 public int getPreference() { 253 return iPreference; 254 } 255 256 public void setPreference(int preference) { 257 iPreference = preference; 258 } 259 260 /** Length */ 261 public int getLength() { 262 return iLength; 263 } 264 265 /** Length */ 266 public int getNrSlotsPerMeeting() { 267 return iLength; 268 } 269 270 /** Normalized preference */ 271 public double getNormalizedPreference() { 272 return iNormalizedPreference; 273 } 274 275 public void setNormalizedPreference(double normalizedPreference) { 276 iNormalizedPreference = normalizedPreference; 277 } 278 279 /** Time pattern model (can be null) */ 280 public Long getTimePatternId() { 281 return iTimePatternId; 282 } 283 284 public Long getDatePatternId() { 285 return iDatePatternId; 286 } 287 288 public void setTimePatternId(Long timePatternId) { 289 iTimePatternId = timePatternId; 290 } 291 292 public BitSet getWeekCode() { 293 return iWeekCode; 294 } 295 296 public String getDatePatternName() { 297 return iDatePatternName; 298 } 299 300 public void setDatePattern(Long datePatternId, String datePatternName, BitSet weekCode) { 301 iDatePatternId = datePatternId; 302 iDatePatternName = datePatternName; 303 iWeekCode = weekCode; 304 } 305 306 public int getDatePatternPreference() { 307 return iDatePreference; 308 } 309 310 @Override 311 public String toString() { 312 return getName() + " (" + iNormalizedPreference + ")"; 313 } 314 315 @Override 316 public int hashCode() { 317 return iHashCode; 318 } 319 320 private class StartSlotsEnum implements IntEnumeration { 321 int day = -1; 322 boolean hasNext = false; 323 324 private StartSlotsEnum() { 325 hasNext = nextDay(); 326 } 327 328 boolean nextDay() { 329 do { 330 day++; 331 if (day >= Constants.DAY_CODES.length) 332 return false; 333 } while ((Constants.DAY_CODES[day] & iDayCode) == 0); 334 return true; 335 } 336 337 @Override 338 public boolean hasMoreElements() { 339 return hasNext; 340 } 341 342 @Override 343 public Integer nextElement() { 344 int slot = (day * Constants.SLOTS_PER_DAY) + iStartSlot; 345 hasNext = nextDay(); 346 return slot; 347 } 348 349 @Deprecated 350 @Override 351 public Integer nextInt() { 352 return nextElement(); 353 } 354 } 355 356 private class DaysEnum extends StartSlotsEnum { 357 private DaysEnum() { 358 super(); 359 } 360 361 @Override 362 public Integer nextElement() { 363 int ret = day; 364 hasNext = nextDay(); 365 return ret; 366 } 367 } 368 369 private class SlotsEnum extends StartSlotsEnum { 370 int pos = 0; 371 372 private SlotsEnum() { 373 super(); 374 } 375 376 private boolean nextSlot() { 377 if (pos + 1 < iLength) { 378 pos++; 379 return true; 380 } 381 if (nextDay()) { 382 pos = 0; 383 return true; 384 } 385 return false; 386 } 387 388 @Override 389 public Integer nextElement() { 390 int slot = (day * Constants.SLOTS_PER_DAY) + iStartSlot + pos; 391 hasNext = nextSlot(); 392 return slot; 393 } 394 } 395 396 @Override 397 public boolean equals(Object o) { 398 if (o == null || !(o instanceof TimeLocation)) 399 return false; 400 TimeLocation t = (TimeLocation) o; 401 if (getStartSlot() != t.getStartSlot()) 402 return false; 403 if (getLength() != t.getLength()) 404 return false; 405 if (getDayCode() != t.getDayCode()) 406 return false; 407 return ToolBox.equals(getTimePatternId(), t.getTimePatternId()) 408 && ToolBox.equals(getDatePatternId(), t.getDatePatternId()); 409 } 410 411 public int getNrWeeks() { 412 return getNrWeeks(0, iWeekCode.size() - 1); 413 } 414 415 public int getNrWeeks(int startDay, int endDay) { 416 /* 417 * BitSet x = new BitSet(1+(endDay-startDay)/Constants.NR_DAYS); for 418 * (int i=iWeekCode.nextSetBit(startDay); i<=endDay && i>=0; 419 * i=iWeekCode.nextSetBit(i+1)) x.set((i-startDay)/Constants.NR_DAYS); 420 * return x.cardinality(); 421 */ 422 int card = iWeekCode.get(startDay, endDay).cardinality(); 423 if (card == 0) 424 return 0; 425 if (card <= 7) 426 return 1; 427 return (5 + card) / 6; 428 } 429 430 public interface IntEnumeration extends Enumeration<Integer> { 431 @Deprecated 432 public Integer nextInt(); 433 } 434 435 private Integer iFirstMeeting = null; 436 public int getFirstMeeting(int dayOfWeekOffset) { 437 if (iFirstMeeting == null) { 438 int idx = -1; 439 while ((idx = getWeekCode().nextSetBit(1 + idx)) >= 0) { 440 int dow = (idx + dayOfWeekOffset) % 7; 441 if ((getDayCode() & Constants.DAY_CODES[dow]) != 0) break; 442 } 443 iFirstMeeting = idx; 444 } 445 return iFirstMeeting; 446 } 447}