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    }