001 package net.sf.cpsolver.coursett.model;
002
003 import java.util.BitSet;
004 import java.util.Enumeration;
005
006 import net.sf.cpsolver.coursett.Constants;
007 import 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 Tomas Muller<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
032 public 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 public int[] getDaysArray() {
227 int[] days = new int[getNrMeetings()];
228 int i = 0;
229 for (Enumeration<Integer> e = getDays(); e.hasMoreElements();)
230 days[i++] = e.nextElement();
231 return days;
232 }
233
234 /** Text representation */
235 public String getName() {
236 return getDayHeader() + " " + getStartTimeHeader();
237 }
238
239 public String getLongName() {
240 return getDayHeader() + " " + getStartTimeHeader() + " - " + getEndTimeHeader() + " " + getDatePatternName();
241 }
242
243 public String getLongNameNoAdj() {
244 return getDayHeader() + " " + getStartTimeHeader() + " - " + getEndTimeHeaderNoAdj() + " "
245 + getDatePatternName();
246 }
247
248 /** Preference */
249 public int getPreference() {
250 return iPreference;
251 }
252
253 public void setPreference(int preference) {
254 iPreference = preference;
255 }
256
257 /** Length */
258 public int getLength() {
259 return iLength;
260 }
261
262 /** Length */
263 public int getNrSlotsPerMeeting() {
264 return iLength;
265 }
266
267 /** Normalized preference */
268 public double getNormalizedPreference() {
269 return iNormalizedPreference;
270 }
271
272 public void setNormalizedPreference(double normalizedPreference) {
273 iNormalizedPreference = normalizedPreference;
274 }
275
276 /** Time pattern model (can be null) */
277 public Long getTimePatternId() {
278 return iTimePatternId;
279 }
280
281 public Long getDatePatternId() {
282 return iDatePatternId;
283 }
284
285 public void setTimePatternId(Long timePatternId) {
286 iTimePatternId = timePatternId;
287 }
288
289 public BitSet getWeekCode() {
290 return iWeekCode;
291 }
292
293 public String getDatePatternName() {
294 return iDatePatternName;
295 }
296
297 public void setDatePattern(Long datePatternId, String datePatternName, BitSet weekCode) {
298 iDatePatternId = datePatternId;
299 iDatePatternName = datePatternName;
300 iWeekCode = weekCode;
301 }
302
303 public int getDatePatternPreference() {
304 return iDatePreference;
305 }
306
307 @Override
308 public String toString() {
309 return getName() + " (" + iNormalizedPreference + ")";
310 }
311
312 @Override
313 public int hashCode() {
314 return iHashCode;
315 }
316
317 private class StartSlotsEnum implements IntEnumeration {
318 int day = -1;
319 boolean hasNext = false;
320
321 private StartSlotsEnum() {
322 hasNext = nextDay();
323 }
324
325 boolean nextDay() {
326 do {
327 day++;
328 if (day >= Constants.DAY_CODES.length)
329 return false;
330 } while ((Constants.DAY_CODES[day] & iDayCode) == 0);
331 return true;
332 }
333
334 @Override
335 public boolean hasMoreElements() {
336 return hasNext;
337 }
338
339 @Override
340 public Integer nextElement() {
341 int slot = (day * Constants.SLOTS_PER_DAY) + iStartSlot;
342 hasNext = nextDay();
343 return slot;
344 }
345
346 @Deprecated
347 @Override
348 public Integer nextInt() {
349 return nextElement();
350 }
351 }
352
353 private class DaysEnum extends StartSlotsEnum {
354 private DaysEnum() {
355 super();
356 }
357
358 @Override
359 public Integer nextElement() {
360 int ret = day;
361 hasNext = nextDay();
362 return ret;
363 }
364 }
365
366 private class SlotsEnum extends StartSlotsEnum {
367 int pos = 0;
368
369 private SlotsEnum() {
370 super();
371 }
372
373 private boolean nextSlot() {
374 if (pos + 1 < iLength) {
375 pos++;
376 return true;
377 }
378 if (nextDay()) {
379 pos = 0;
380 return true;
381 }
382 return false;
383 }
384
385 @Override
386 public Integer nextElement() {
387 int slot = (day * Constants.SLOTS_PER_DAY) + iStartSlot + pos;
388 hasNext = nextSlot();
389 return slot;
390 }
391 }
392
393 @Override
394 public boolean equals(Object o) {
395 if (o == null || !(o instanceof TimeLocation))
396 return false;
397 TimeLocation t = (TimeLocation) o;
398 if (getStartSlot() != t.getStartSlot())
399 return false;
400 if (getLength() != t.getLength())
401 return false;
402 if (getDayCode() != t.getDayCode())
403 return false;
404 return ToolBox.equals(getTimePatternId(), t.getTimePatternId())
405 && ToolBox.equals(getDatePatternId(), t.getDatePatternId());
406 }
407
408 public int getNrWeeks() {
409 return getNrWeeks(0, iWeekCode.size() - 1);
410 }
411
412 public int getNrWeeks(int startDay, int endDay) {
413 /*
414 * BitSet x = new BitSet(1+(endDay-startDay)/Constants.NR_DAYS); for
415 * (int i=iWeekCode.nextSetBit(startDay); i<=endDay && i>=0;
416 * i=iWeekCode.nextSetBit(i+1)) x.set((i-startDay)/Constants.NR_DAYS);
417 * return x.cardinality();
418 */
419 int card = iWeekCode.get(startDay, endDay).cardinality();
420 if (card == 0)
421 return 0;
422 if (card <= 7)
423 return 1;
424 return (5 + card) / 6;
425 }
426
427 public interface IntEnumeration extends Enumeration<Integer> {
428 @Deprecated
429 public Integer nextInt();
430 }
431 }